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
@@ -0,0 +1,155 @@
|
|||||||
|
"""Adim denetimi: ek modelin her adimdaki cevabini Jev gecirir ya da gecirmez (11 Ekim 2026).
|
||||||
|
|
||||||
|
Kullanici karari (11 Ekim 2026): "Bu kopruye Jev koy, her model her adimda kontrol etsin ve yakaladigini qwen 3.8
|
||||||
|
obliterated thinking versin." / "Bir prompt cok adimli olabilir; her adim kontrol edilsin. Jev sadece geciren ya da
|
||||||
|
gecirmeyen; gecirmezse 3.8 obliterated thinking." Ardindan (ayni gun): "obliterated:thinking yerine Uncensored-Uygulama yapar
|
||||||
|
misin" -> yedek, Uncensored-Uygulama combosudur. Yani denetim adim basinadir: Jev'in gecirmedigi adimi yedek model
|
||||||
|
yapar, sonraki adim yine secili modele gider ve yine denetlenir.
|
||||||
|
|
||||||
|
Jev (TypeSafe System One) metin uretmez; bir duruma sorulan evet/hayir sorularina olasilik verir (olcum 11 Ekim
|
||||||
|
2026: cagri basina ~0,4 sn, 13 gercek ornegin 13'u dogru ayrildi). Kural kodda durur:
|
||||||
|
- yakalanir: cevap istegi yapmiyor/soylemiyor (ret, kacamak, konuyu degistirme) ya da istegi sormadan kendi
|
||||||
|
sartlariyla degistiriyor;
|
||||||
|
- yakalanmaz: somut bir teknik engel bildiren cevap (hata, eksik dosya/arac/yetki) ve kullaniciya soru ya da onay
|
||||||
|
soran cevap; metni olmayan (yalniz arac cagiran) adim.
|
||||||
|
|
||||||
|
Jev'e ulasilamazsa cevap oldugu gibi gecer (is durmaz); art arda hatada bir sure hic sorulmaz.
|
||||||
|
Codex'in kendi modelleri kopruye ugramadigi icin bu denetimin disindadir.
|
||||||
|
|
||||||
|
Ayar (bridge.json, istege bagli): "jev_denetim": {"enabled": false} kapatir; "hedef" yedek modelin Codex adi;
|
||||||
|
"ret", "sart", "teknik", "soru" esikleri; "sure" saniye.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
ADRES = "https://api.typesafe.ai/v1/systemone"
|
||||||
|
MODEL = "jev-1.13.0"
|
||||||
|
HEDEF = "ide/Uncensored-Uygulama"
|
||||||
|
ESIK = {"ret": 0.7, "sart": 0.9, "teknik": 0.5, "soru": 0.5}
|
||||||
|
SURE = 6.0
|
||||||
|
EN_COK_ISTEK = 3000
|
||||||
|
EN_COK_CEVAP = 6000
|
||||||
|
DEVRE_HATA = 3 # art arda bu kadar hatadan sonra
|
||||||
|
DEVRE_SN = 300 # bu kadar sure Jev'e sorulmaz
|
||||||
|
SORU = {
|
||||||
|
"ret": ("The assistant did NOT do what the user asked or did NOT answer the user's question: it refused, declined, "
|
||||||
|
"said it cannot or will not, avoided giving the requested judgment, or redirected to a different topic."),
|
||||||
|
"sart": ("The assistant changed the request on its own: it added its own conditions, softened or relabeled what was "
|
||||||
|
"asked, or left part of it out, instead of doing exactly what the user asked."),
|
||||||
|
"teknik": ("The assistant reports a concrete technical obstacle as the reason: an error message, a failed command, "
|
||||||
|
"a missing file, tool, permission, connection or piece of information."),
|
||||||
|
"soru": ("The assistant's reply is mainly a question to the user: it asks for a clarification, a choice or an "
|
||||||
|
"approval and waits for the user's answer before doing anything."),
|
||||||
|
}
|
||||||
|
_KILIT = threading.Lock()
|
||||||
|
_DEVRE = {"hata": 0, "kadar": 0.0}
|
||||||
|
_ANAHTAR = {"okundu": False, "deger": ""}
|
||||||
|
|
||||||
|
|
||||||
|
def anahtar(ortam=None, dosyalar=None):
|
||||||
|
"""TypeSafe anahtari: ortam degiskeni, sonra ~/.typesafe/typesafe.env. Yoksa "" (denetim kapali sayilir)."""
|
||||||
|
ortam = os.environ if ortam is None else ortam
|
||||||
|
deger = str(ortam.get("TYPESAFE_API_KEY") or "").strip()
|
||||||
|
if deger:
|
||||||
|
return deger
|
||||||
|
if dosyalar is None:
|
||||||
|
if _ANAHTAR["okundu"]:
|
||||||
|
return _ANAHTAR["deger"]
|
||||||
|
dosyalar = [os.path.join(os.path.expanduser("~"), ".typesafe", "typesafe.env")]
|
||||||
|
onbellek = True
|
||||||
|
else:
|
||||||
|
onbellek = False
|
||||||
|
for yol in dosyalar:
|
||||||
|
try:
|
||||||
|
with open(yol, encoding="utf-8", errors="replace") as f:
|
||||||
|
for satir in f:
|
||||||
|
m = re.match(r"^\s*TYPESAFE_API_KEY\s*=\s*(.*)$", satir)
|
||||||
|
if m:
|
||||||
|
deger = m.group(1).strip().strip('"').strip("'")
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
if deger:
|
||||||
|
break
|
||||||
|
if onbellek:
|
||||||
|
_ANAHTAR.update(okundu=True, deger=deger)
|
||||||
|
return deger
|
||||||
|
|
||||||
|
|
||||||
|
def _metin(oge):
|
||||||
|
icerik = oge.get("content")
|
||||||
|
if isinstance(icerik, str):
|
||||||
|
return icerik
|
||||||
|
return "".join(str(p.get("text") or "") for p in icerik or [] if isinstance(p, dict))
|
||||||
|
|
||||||
|
|
||||||
|
def son_istek(req):
|
||||||
|
"""Kullanicinin bu turdaki istegi: son kullanici mesaji. Sesli aktarimda yalniz <input> icindeki soz;
|
||||||
|
baska sarmalli sistem mesajlari (kanca baglami) atlanir."""
|
||||||
|
for oge in reversed(req.get("input") or []):
|
||||||
|
if not (isinstance(oge, dict) and oge.get("type", "message") == "message" and oge.get("role") == "user"):
|
||||||
|
continue
|
||||||
|
metin = _metin(oge).strip()
|
||||||
|
m = re.search(r"<realtime_delegation>.*?<input>(.*?)</input>", metin, re.S)
|
||||||
|
if m:
|
||||||
|
return m.group(1).strip()[:EN_COK_ISTEK]
|
||||||
|
if metin and not metin.startswith("<"):
|
||||||
|
return metin[:EN_COK_ISTEK]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _kisalt(metin, sinir):
|
||||||
|
if len(metin) <= sinir:
|
||||||
|
return metin
|
||||||
|
bas = sinir * 2 // 3
|
||||||
|
return metin[:bas] + "\n[...]\n" + metin[-(sinir - bas):]
|
||||||
|
|
||||||
|
|
||||||
|
def _gonder(govde, key, sure):
|
||||||
|
istek = urllib.request.Request(ADRES, data=json.dumps(govde).encode("utf-8"), method="POST",
|
||||||
|
headers={"Authorization": "Bearer " + key, "Content-Type": "application/json"})
|
||||||
|
with urllib.request.urlopen(istek, timeout=sure) as yanit:
|
||||||
|
return json.load(yanit)
|
||||||
|
|
||||||
|
|
||||||
|
def sor(istek, cevap, key, sure=SURE, gonder=_gonder, simdi=None):
|
||||||
|
"""Jev'e dort soruyu sorar. Donus: {"ret","sart","teknik","soru"} olasiliklari | None (sorulamadi)."""
|
||||||
|
simdi = time.time() if simdi is None else simdi
|
||||||
|
with _KILIT:
|
||||||
|
if simdi < _DEVRE["kadar"]:
|
||||||
|
return None
|
||||||
|
govde = {"model": MODEL, "state": {"user_request": istek, "assistant_reply": _kisalt(cevap, EN_COK_CEVAP)},
|
||||||
|
"questions": {k: {"type": "noul", "instructions": v} for k, v in SORU.items()}}
|
||||||
|
try:
|
||||||
|
yanitlar = (gonder(govde, key, sure) or {}).get("answers") or {}
|
||||||
|
sonuc = {k: float(yanitlar[k]["noul"]) for k in SORU}
|
||||||
|
except Exception:
|
||||||
|
with _KILIT:
|
||||||
|
_DEVRE["hata"] += 1
|
||||||
|
if _DEVRE["hata"] >= DEVRE_HATA:
|
||||||
|
_DEVRE.update(hata=0, kadar=simdi + DEVRE_SN)
|
||||||
|
return None
|
||||||
|
with _KILIT:
|
||||||
|
_DEVRE["hata"] = 0
|
||||||
|
return sonuc
|
||||||
|
|
||||||
|
|
||||||
|
def karar(puan, ayar=None):
|
||||||
|
"""True: adim gecmez (yedek model yapar). Esikler ayardan degistirilebilir."""
|
||||||
|
e = dict(ESIK)
|
||||||
|
e.update({k: float(v) for k, v in (ayar or {}).items() if k in ESIK})
|
||||||
|
if puan["soru"] >= e["soru"]:
|
||||||
|
return False # kullaniciya soru/onay soruyor: kullanici cevaplar
|
||||||
|
if puan["ret"] >= e["ret"] and puan["teknik"] < e["teknik"]:
|
||||||
|
return True # yapmiyor / soylemiyor (teknik engel degil)
|
||||||
|
return puan["sart"] >= e["sart"] # sormadan kendi sartlariyla degistirdi
|
||||||
|
|
||||||
|
|
||||||
|
def ayar_oku(cfg):
|
||||||
|
"""(acik, hedef slug, ayar sozlugu)."""
|
||||||
|
ayar = cfg if isinstance(cfg, dict) else {}
|
||||||
|
return ayar.get("enabled", True) is not False, str(ayar.get("hedef") or HEDEF), ayar
|
||||||
@@ -30,6 +30,7 @@ import yarim
|
|||||||
import model_gecis
|
import model_gecis
|
||||||
import is_hatti
|
import is_hatti
|
||||||
import internet
|
import internet
|
||||||
|
import jev_denetim
|
||||||
|
|
||||||
SURUM = "8"
|
SURUM = "8"
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
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)
|
n = int(self.headers.get("Content-Length") or 0)
|
||||||
return self.rfile.read(n) if n else b""
|
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)
|
self.send_response(status)
|
||||||
for k, v in resp.headers.items():
|
for k, v in resp.headers.items():
|
||||||
if k.lower() in RESP_HOP: continue
|
if k.lower() in RESP_HOP: continue
|
||||||
self.send_header(k, v)
|
self.send_header(k, v)
|
||||||
self.send_header("Transfer-Encoding", "chunked")
|
self.send_header("Transfer-Encoding", "chunked")
|
||||||
self.end_headers()
|
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
|
total = 0
|
||||||
bas = b"" # ust kaynagin ilk baytlari: cok kisa (bos) akislarin nedeni gunluge yazilir
|
bas = b"" # ust kaynagin ilk baytlari: cok kisa (bos) akislarin nedeni gunluge yazilir
|
||||||
def w(b):
|
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,
|
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]),
|
"ide_model": int((sayi.split() or ["0"])[0]),
|
||||||
"ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False)),
|
"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"):
|
if self.command == "POST" and path.rstrip("/").endswith("/stop"):
|
||||||
self._send_json(200, {"ok": True})
|
self._send_json(200, {"ok": True})
|
||||||
log("AITURK IDE istegiyle kapatiliyor")
|
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.")
|
"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")
|
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")
|
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:
|
if DEBUG:
|
||||||
open(os.path.join(LOGDIR, f"{n:04d}_req.json"), "wb").write(data)
|
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}
|
hdrs = {"Authorization": f"Bearer {route['key']}", "Content-Type": "application/json", "Accept": "text/event-stream", "User-Agent": UA}
|
||||||
last = None
|
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
|
# 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);
|
# 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.
|
# 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)
|
sonuc = self._stream(resp, resp.status, tag, akis)
|
||||||
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
|
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
|
||||||
return sonuc
|
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)
|
return self._stream(resp, resp.status, tag, rewriter, izle)
|
||||||
self._send_json(503, {"error": {"message": f"bridge: hedef ulasilamadi {last}"}})
|
self._send_json(503, {"error": {"message": f"bridge: hedef ulasilamadi {last}"}})
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class AccessPrograms(unittest.TestCase):
|
|||||||
"fix_tool_ids": lambda req: 0,
|
"fix_tool_ids": lambda req: 0,
|
||||||
"drop_foreign_items": lambda req: 0,
|
"drop_foreign_items": lambda req: 0,
|
||||||
"SseRewriter": Mock(),
|
"SseRewriter": Mock(),
|
||||||
"log": lambda message: None, "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "arac_semalarini_duzelt": lambda req: 0, "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
"log": lambda message: None, "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "jev_denetim": __import__("jev_denetim"), "internet": __import__("internet"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "arac_semalarini_duzelt": lambda req: 0, "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
||||||
"DEBUG": False, "UA": "test", "RETRIES": 1, "UPSTREAM_IDLE_TIMEOUT": 3900,
|
"DEBUG": False, "UA": "test", "RETRIES": 1, "UPSTREAM_IDLE_TIMEOUT": 3900,
|
||||||
}
|
}
|
||||||
exec(compile(ast.Module(body=taraf + [flatten, claude_sonu, responses, ozgun], type_ignores=[]), "bridge.py", "exec"), self.env)
|
exec(compile(ast.Module(body=taraf + [flatten, claude_sonu, responses, ozgun], type_ignores=[]), "bridge.py", "exec"), self.env)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ComboNotResent(unittest.TestCase):
|
|||||||
"fix_tool_ids": lambda req: 0, "drop_foreign_items": lambda req: 0,
|
"fix_tool_ids": lambda req: 0, "drop_foreign_items": lambda req: 0,
|
||||||
"SseRewriter": Mock(), "log": lambda message: None,
|
"SseRewriter": Mock(), "log": lambda message: None,
|
||||||
"DEBUG": False, "UA": "test", "RETRIES": 6, "UPSTREAM_IDLE_TIMEOUT": 3600,
|
"DEBUG": False, "UA": "test", "RETRIES": 6, "UPSTREAM_IDLE_TIMEOUT": 3600,
|
||||||
"NO_RETRY_PATTERNS": [], "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "arac_semalarini_duzelt": lambda req: 0, "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
"NO_RETRY_PATTERNS": [], "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "jev_denetim": __import__("jev_denetim"), "internet": __import__("internet"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "arac_semalarini_duzelt": lambda req: 0, "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
||||||
}
|
}
|
||||||
exec(compile(ast.Module(body=[pick("ek_model_mu"), pick("yonlendirilen"), pick("flatten_request"), pick("claude_sonu_duzelt"),
|
exec(compile(ast.Module(body=[pick("ek_model_mu"), pick("yonlendirilen"), pick("flatten_request"), pick("claude_sonu_duzelt"),
|
||||||
pick("is_timeout"), pick("kopma_mi"), responses],
|
pick("is_timeout"), pick("kopma_mi"), responses],
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ class Zincir(unittest.TestCase):
|
|||||||
"decompress": lambda body, enc: body, "MODEL_MAP": {}, "ROUTES": {}, "CFG": {"model_bilgi": False}, "log": lambda m: None,
|
"decompress": lambda body, enc: body, "MODEL_MAP": {}, "ROUTES": {}, "CFG": {"model_bilgi": False}, "log": lambda m: None,
|
||||||
"model_gecis": model_gecis, "YONLENDIRME": model_gecis.Yonlendirme(None), "bilinen_modeller": lambda: MODELS,
|
"model_gecis": model_gecis, "YONLENDIRME": model_gecis.Yonlendirme(None), "bilinen_modeller": lambda: MODELS,
|
||||||
"model_adi": lambda slug: dict(MODELS).get(slug, slug), "is_hatti": is_hatti, "GORULEN_ISTEMCI": set(),
|
"model_adi": lambda slug: dict(MODELS).get(slug, slug), "is_hatti": is_hatti, "GORULEN_ISTEMCI": set(),
|
||||||
"yarim": yarim, "ozet_kopru": ozet_kopru, "OZET_FRENI": ozet_kopru.DonguFreni(), "pencere": pencere,
|
"yarim": yarim, "jev_denetim": __import__("jev_denetim"), "internet": __import__("internet"), "ozet_kopru": ozet_kopru, "OZET_FRENI": ozet_kopru.DonguFreni(), "pencere": pencere,
|
||||||
"fix_tool_ids": lambda req: 0, "drop_foreign_items": lambda req: 0, "IDE_BILGI": {}, "EXTRA_MODELS": [],
|
"fix_tool_ids": lambda req: 0, "drop_foreign_items": lambda req: 0, "IDE_BILGI": {}, "EXTRA_MODELS": [],
|
||||||
"arac_semalarini_duzelt": lambda req: 0, "SseRewriter": Mock(), "DEBUG": False, "UA": "test", "RETRIES": 6,
|
"arac_semalarini_duzelt": lambda req: 0, "SseRewriter": Mock(), "DEBUG": False, "UA": "test", "RETRIES": 6,
|
||||||
"UPSTREAM_IDLE_TIMEOUT": 1, "NO_RETRY_PATTERNS": [], "pano_blogunu_ulastir": lambda req: (0, ""),
|
"UPSTREAM_IDLE_TIMEOUT": 1, "NO_RETRY_PATTERNS": [], "pano_blogunu_ulastir": lambda req: (0, ""),
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
"""Step check (11 Oct 2026): Jev passes or blocks each step of an added model; a blocked step is redone by the
|
||||||
|
fallback model, the next step goes back to the selected model."""
|
||||||
|
import ast
|
||||||
|
import http.client
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
import jev_denetim
|
||||||
|
import yarim
|
||||||
|
|
||||||
|
SOURCE = Path(__file__).with_name("kopru.py").read_text(encoding="utf-8")
|
||||||
|
tree = ast.parse(SOURCE)
|
||||||
|
handler = next(n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "H")
|
||||||
|
|
||||||
|
|
||||||
|
def method(name):
|
||||||
|
return next(n for n in handler.body if isinstance(n, ast.FunctionDef) and n.name == name)
|
||||||
|
|
||||||
|
|
||||||
|
def user(text):
|
||||||
|
return {"type": "message", "role": "user", "content": [{"type": "input_text", "text": text}]}
|
||||||
|
|
||||||
|
|
||||||
|
def sse(*deltas, done=True):
|
||||||
|
out = b""
|
||||||
|
for d in deltas:
|
||||||
|
out += b"event: response.output_text.delta\ndata: " + json.dumps(
|
||||||
|
{"type": "response.output_text.delta", "delta": d}).encode() + b"\n\n"
|
||||||
|
if done:
|
||||||
|
out += b'event: response.completed\ndata: {"type": "response.completed"}\n\n'
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class Yanit:
|
||||||
|
"""Fake upstream response: read1 returns the body in two pieces."""
|
||||||
|
status = 200
|
||||||
|
|
||||||
|
def __init__(self, body):
|
||||||
|
self.parts = [body[:len(body) // 2], body[len(body) // 2:]]
|
||||||
|
self.headers = {"Content-Type": "text/event-stream"}
|
||||||
|
|
||||||
|
def read1(self, n):
|
||||||
|
return self.parts.pop(0) if self.parts else b""
|
||||||
|
|
||||||
|
|
||||||
|
def scores(ret=0.0, sart=0.0, teknik=0.0, soru=0.0):
|
||||||
|
return {"ret": ret, "sart": sart, "teknik": teknik, "soru": soru}
|
||||||
|
|
||||||
|
|
||||||
|
class Karar(unittest.TestCase):
|
||||||
|
def test_refusals_and_silent_conditions_are_blocked(self):
|
||||||
|
self.assertTrue(jev_denetim.karar(scores(ret=0.84))) # "böyle bir hüküm vermiyorum"
|
||||||
|
self.assertTrue(jev_denetim.karar(scores(ret=0.41, sart=0.95))) # did it with its own conditions
|
||||||
|
self.assertTrue(jev_denetim.karar(scores(ret=0.93, sart=0.72)))
|
||||||
|
|
||||||
|
def test_normal_answers_technical_obstacles_and_questions_pass(self):
|
||||||
|
self.assertFalse(jev_denetim.karar(scores(ret=0.05, sart=0.3)))
|
||||||
|
self.assertFalse(jev_denetim.karar(scores(ret=0.85, teknik=0.97))) # "SSH key rejected, could not read"
|
||||||
|
self.assertFalse(jev_denetim.karar(scores(ret=0.62, sart=0.91, soru=0.93))) # asks for approval first
|
||||||
|
self.assertFalse(jev_denetim.karar(scores(ret=0.48, soru=0.96)))
|
||||||
|
|
||||||
|
def test_thresholds_come_from_settings(self):
|
||||||
|
self.assertFalse(jev_denetim.karar(scores(ret=0.84), {"ret": 0.9}))
|
||||||
|
self.assertTrue(jev_denetim.karar(scores(sart=0.8), {"sart": 0.75}))
|
||||||
|
|
||||||
|
|
||||||
|
class Istek(unittest.TestCase):
|
||||||
|
def test_last_user_request_is_taken_and_wrappers_are_skipped(self):
|
||||||
|
voice = user("<realtime_delegation>\n <input>siteyi yap</input>\n <transcript_delta>x</transcript_delta>\n</realtime_delegation>")
|
||||||
|
self.assertEqual(jev_denetim.son_istek({"input": [user("eski"), voice]}), "siteyi yap")
|
||||||
|
hook = user("<hook_context>pano</hook_context>")
|
||||||
|
call = {"type": "function_call", "call_id": "c", "name": "shell", "arguments": "{}"}
|
||||||
|
self.assertEqual(jev_denetim.son_istek({"input": [user("README'yi düzelt"), call, hook]}), "README'yi düzelt")
|
||||||
|
self.assertEqual(jev_denetim.son_istek({"input": [call]}), "")
|
||||||
|
|
||||||
|
|
||||||
|
class Sor(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
jev_denetim._DEVRE.update(hata=0, kadar=0.0)
|
||||||
|
|
||||||
|
def test_request_shape_and_scores(self):
|
||||||
|
seen = {}
|
||||||
|
|
||||||
|
def gonder(govde, key, sure):
|
||||||
|
seen.update(govde=govde, key=key)
|
||||||
|
return {"answers": {k: {"type": "noul", "noul": 0.25} for k in jev_denetim.SORU}}
|
||||||
|
puan = jev_denetim.sor("istek", "c" * 9000, "anahtar", gonder=gonder)
|
||||||
|
self.assertEqual(puan, scores(0.25, 0.25, 0.25, 0.25))
|
||||||
|
self.assertEqual(seen["key"], "anahtar")
|
||||||
|
self.assertEqual(seen["govde"]["state"]["user_request"], "istek")
|
||||||
|
self.assertLess(len(seen["govde"]["state"]["assistant_reply"]), 6100) # long answers are cut
|
||||||
|
self.assertEqual(set(seen["govde"]["questions"]), {"ret", "sart", "teknik", "soru"})
|
||||||
|
|
||||||
|
def test_failures_pass_the_answer_and_open_the_breaker(self):
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def bozuk(govde, key, sure):
|
||||||
|
calls.append(1)
|
||||||
|
raise OSError("ulasilamadi")
|
||||||
|
for _ in range(jev_denetim.DEVRE_HATA):
|
||||||
|
self.assertIsNone(jev_denetim.sor("i", "c", "k", gonder=bozuk, simdi=1000.0))
|
||||||
|
self.assertIsNone(jev_denetim.sor("i", "c", "k", gonder=bozuk, simdi=1001.0)) # breaker open: not called
|
||||||
|
self.assertEqual(len(calls), jev_denetim.DEVRE_HATA)
|
||||||
|
self.assertIsNone(jev_denetim.sor("i", "c", "k", gonder=bozuk, simdi=1000.0 + jev_denetim.DEVRE_SN + 1))
|
||||||
|
self.assertEqual(len(calls), jev_denetim.DEVRE_HATA + 1)
|
||||||
|
|
||||||
|
def test_key_is_read_from_env_or_file(self):
|
||||||
|
self.assertEqual(jev_denetim.anahtar({"TYPESAFE_API_KEY": " a "}), "a")
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
yol = os.path.join(d, "typesafe.env")
|
||||||
|
Path(yol).write_text('TYPESAFE_API_KEY="b"\n', encoding="utf-8")
|
||||||
|
self.assertEqual(jev_denetim.anahtar({}, [yol]), "b")
|
||||||
|
self.assertEqual(jev_denetim.anahtar({}, [os.path.join(d, "yok.env")]), "")
|
||||||
|
|
||||||
|
def test_settings(self):
|
||||||
|
self.assertEqual(jev_denetim.ayar_oku(None)[:2], (True, "ide/Uncensored-Uygulama"))
|
||||||
|
self.assertEqual(jev_denetim.ayar_oku({"enabled": False, "hedef": "ide/x"})[:2], (False, "ide/x"))
|
||||||
|
|
||||||
|
|
||||||
|
class Akis(unittest.TestCase):
|
||||||
|
"""_denetimli_akis through a fake handler: what reaches the client."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.logs = []
|
||||||
|
self.asked = []
|
||||||
|
self.puan = scores()
|
||||||
|
sahte = Mock()
|
||||||
|
sahte.SURE = 6.0
|
||||||
|
sahte.anahtar = lambda: "k"
|
||||||
|
sahte.sor = lambda istek, metin, key, sure: (self.asked.append((istek, metin)), self.puan)[1]
|
||||||
|
sahte.karar = jev_denetim.karar
|
||||||
|
env = {"jev_denetim": sahte, "yarim": yarim, "time": time, "http": __import__("http"), "log": self.logs.append,
|
||||||
|
"RESP_HOP": set(), "json": json}
|
||||||
|
adlar = ("_akis_basligi", "_denetimli_akis", "_stream")
|
||||||
|
exec(compile(ast.Module(body=[method(a) for a in adlar], type_ignores=[]), "kopru.py", "exec"), env)
|
||||||
|
self.h = type("H", (), {a: env[a] for a in adlar})()
|
||||||
|
self.h.wfile = io.BytesIO()
|
||||||
|
self.h.send_response = Mock()
|
||||||
|
self.h.send_header = Mock()
|
||||||
|
self.h.end_headers = Mock()
|
||||||
|
|
||||||
|
def sent(self):
|
||||||
|
"""Chunked body the client received, decoded."""
|
||||||
|
raw, out = self.h.wfile.getvalue(), b""
|
||||||
|
while raw:
|
||||||
|
size, _, rest = raw.partition(b"\r\n")
|
||||||
|
n = int(size, 16)
|
||||||
|
if n == 0:
|
||||||
|
break
|
||||||
|
out += rest[:n]
|
||||||
|
raw = rest[n + 2:]
|
||||||
|
return out
|
||||||
|
|
||||||
|
def test_a_passed_step_is_delivered_unchanged(self):
|
||||||
|
body = sse("Fenerbahçe'nin ", "13 puanı var.")
|
||||||
|
self.h._denetimli_akis(Yanit(body), "[1]", None, None, "kaç puan", {}, lambda: self.fail("fallback opened"))
|
||||||
|
self.assertEqual(self.sent(), body)
|
||||||
|
self.assertEqual(self.asked, [("kaç puan", "Fenerbahçe'nin 13 puanı var.")])
|
||||||
|
self.assertIn("gecti", self.logs[0])
|
||||||
|
|
||||||
|
def test_a_blocked_step_is_dropped_and_redone_by_the_fallback(self):
|
||||||
|
self.puan = scores(ret=0.9)
|
||||||
|
yedek = sse("Tamam, yaptım.")
|
||||||
|
self.h._denetimli_akis(Yanit(sse("Bunu yapamam.")), "[1]", None, None, "yap", {},
|
||||||
|
lambda: (Yanit(yedek), None, "[1 yedek]"))
|
||||||
|
self.assertEqual(self.sent(), yedek) # the refused text never reaches the client
|
||||||
|
self.assertNotIn(b"yapamam", self.h.wfile.getvalue())
|
||||||
|
self.assertTrue(any("GECMEDI" in s for s in self.logs))
|
||||||
|
self.h.send_response.assert_called_once() # headers once, although two upstream answers
|
||||||
|
|
||||||
|
def test_a_step_without_text_is_not_sent_to_jev(self):
|
||||||
|
body = b'event: response.output_item.done\ndata: {"type": "response.output_item.done", "item": {"type": "function_call"}}\n\n' + sse()
|
||||||
|
self.h._denetimli_akis(Yanit(body), "[1]", None, None, "yap", {}, lambda: self.fail("fallback opened"))
|
||||||
|
self.assertEqual(self.sent(), body)
|
||||||
|
self.assertEqual(self.asked, [])
|
||||||
|
|
||||||
|
def test_unreachable_jev_or_fallback_keeps_the_first_answer(self):
|
||||||
|
self.puan = None
|
||||||
|
body = sse("Bunu yapamam.")
|
||||||
|
self.h._denetimli_akis(Yanit(body), "[1]", None, None, "yap", {}, lambda: self.fail("fallback opened"))
|
||||||
|
self.assertEqual(self.sent(), body)
|
||||||
|
self.h.wfile = io.BytesIO()
|
||||||
|
self.puan = scores(ret=0.9)
|
||||||
|
|
||||||
|
def bozuk():
|
||||||
|
raise OSError("yedek acilamadi")
|
||||||
|
self.h._denetimli_akis(Yanit(body), "[1]", None, None, "yap", {}, bozuk)
|
||||||
|
self.assertEqual(self.sent(), body)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in new issue
Block a user