feat(codex-bridge): decision-model stages, Dify rules and beta.39

Owner request, 14 October 2026: use the decision model (Jev / Celeris) at
several stages, let Dify supply the rules, code it without new bugs.
Every new stage is switched on in Settings > Kademeli Gecis and starts
disabled, so the upgrade changes no behaviour by itself.

- Settings schema 2 (Python and TypeScript validate the same rules); a
  schema 1 file is filled from the defaults. Default chain is now
  Katman-1 -> Katman-2 -> Katman-3 with Katman-3, Chat-Model and
  bekci-sesli-bekci always passing (owner decision of the same day).
- watchModels: the Bekci correction channel follows a settings list
  instead of the hard-coded Sesli-Hizli-Zeki.
- prefilter: a predicted refusal starts the step at the chosen layer.
- autoRoute: virtual "Otomatik" model; one decision per user turn picks
  the combo, tool steps of the same turn keep it.
- finishCheck: an answer that stops early goes back to the same model once.
- A layer whose window cannot hold the request is skipped.
- policy: stage rules from a Dify workflow (karar_politikasi.py), fetched
  in the background, validated, last good copy kept; turns never wait.
- karar_sor.py: verifierCheck for the dynamic-workflow acceptance step.
- Bridge version 19, IDE 1.0.0-beta.39.

Tests: 299 bridge tests, 36 electron tests; live run on a side bridge
(port 8797) with Celeris and the AITURK gateway for every stage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-14 07:48:33 +03:00
1 parent 8f53b090f7
commit 7c4ef18df0
18 files changed
+1672 -119

No files matched your search

+161 -9
View File
@@ -34,13 +34,15 @@ import model_gecis
import is_hatti
import internet
import jev_denetim
import jev_settings
import karar_politikasi
from bekci_watch import MetadataFilter
# Installed only by owned native launcher, never an HTTP control endpoint.
WATCH_OWNER = None
SURUM = "18"
SURUM = "19"
HERE = os.path.dirname(os.path.abspath(__file__))
try:
with open(os.path.abspath(__file__), "rb") as _f:
@@ -228,6 +230,22 @@ def load_ide_catalog(ag=True):
"kaynak": g.get("pencere_kaynak") or "", "claude": bool(g.get("claude"))}
model_map[slug] = (IDE_ROUTE, g["id"], None)
n += 1
oto = _oto_ayari()
if oto and IDE_PREFIX + oto["model"] not in model_map:
# Sanal model: istek geldiginde karar modeli ayardaki combo'lardan birini secer (14 Ekim 2026).
slug = IDE_PREFIX + oto["model"]
pencereler = [IDE_BILGI.get(IDE_PREFIX + h, {}).get("pencere") for h in set(oto["targets"].values())]
pencere_oto = min(pencereler) if pencereler and all(pencereler) else None
extra_models.append({"slug": slug, "display_name": f"{oto['model']} (karar modeli seçer)",
"description": "Her turda karar modeli işin türüne göre combo seçer: "
+ ", ".join(f"{k}: {v}" for k, v in oto["targets"].items()),
"route": IDE_ROUTE, "target": oto["model"], "clone_from": clone, "priority": 299,
"reasoning_levels": levels, "kind": "combo", "context_window": pencere_oto})
IDE_BILGI[slug] = {"pencere": pencere_oto, "gorsel": all(IDE_BILGI.get(IDE_PREFIX + h, {}).get("gorsel")
for h in set(oto["targets"].values())),
"arac": True, "kaynak": "", "claude": False}
model_map[slug] = (IDE_ROUTE, oto["model"], None)
n += 1
else:
ROUTES.pop(IDE_ROUTE, None)
MODEL_MAP = model_map
@@ -237,10 +255,94 @@ def load_ide_catalog(ag=True):
IDE_DURUM[0] = ozet
log(f"AITURK IDE Codex Baglama: {ozet}")
def _oto_ayari():
"""autoRoute acik ve Kademeli Gecis acikken onun ayari; degilse None. Ayar okunamazsa None."""
try:
s = jev_settings.read()[0]
except Exception:
return None
return s["autoRoute"] if s["enabled"] and s["autoRoute"]["enabled"] else None
# Tur basi kararlari: sohbet -> (tur anahtari, gelen slug, secilen slug). Ayni turun arac adimlari ayni karari kullanir.
ON_KARAR = {}
ON_KARAR_SINIR = 500
# Acilista pencere verisi icin aga cikilmaz (IDE koprunun dinlemeye baslamasini sinirli sure bekler);
# ilk tam yenileme asagidaki dongunun basinda, arka planda yapilir.
load_ide_catalog(ag=False)
def karar_asamasi(n, req, sohbet, slug):
"""14 Ekim 2026 karar asamalari: "Otomatik" modelin combo secimi (autoRoute) ve normal modelin reddedecegi
tahmin edilen isi dogrudan ayardaki katmana verme (prefilter). Karar tur basinda bir kez verilir; ayni turun
arac adimlari ayni karari kullanir (her adimda model degisirse saglayicilar arasi gecmis bozulur). Kapali ayar,
bos istek ya da karar modeli hatasi bugunku yolu degistirmez (Otomatik icin guclu hedef secilir)."""
if not slug.startswith(IDE_PREFIX):
return slug
settings = jev_settings.read()[0]
if not settings["enabled"]:
return slug
a, p = settings["autoRoute"], settings["prefilter"]
oto = a["enabled"] and slug == IDE_PREFIX + a["model"]
hedef = slug[len(IDE_PREFIX):]
ret = p["enabled"] and (oto or not (jev_denetim.muaf_mi(hedef) or hedef == p["target"]
or hedef in jev_denetim.sonrakiler(p["target"])))
if not oto and not ret:
return slug
istek, sira = jev_denetim.son_istek_sirasi(req)
# Tur = son kullanici mesaji (metni ve girdideki yeri); ayni turun arac adimlarinda degismez.
tur = hashlib.sha256(f"{sohbet}\0{sira}\0{istek}".encode("utf-8", "replace")).hexdigest()
kayit = ON_KARAR.get(sohbet) if sohbet else None
if kayit and kayit[0] == tur and kayit[1] == slug:
secilen = kayit[2]
else:
if istek:
t0 = time.time()
sonuc = jev_denetim.on_karar(istek, oto, ret, jev_denetim.anahtar())
puan = sonuc["puan"]
log(f"[{n} {slug}] tur basi karari ({time.time() - t0:.1f} sn): "
+ (", ".join(f"{k}={v:.2f}" for k, v in puan.items()) if puan else "karar modeline sorulamadi"))
else:
sonuc = {"hedef": a["targets"]["zor"] if oto else None, "ret": None}
if oto:
hedef = sonuc["hedef"]
secilen = IDE_PREFIX + hedef
if (p["enabled"] and sonuc["ret"] and not jev_denetim.muaf_mi(hedef) and hedef != p["target"]
and hedef not in jev_denetim.sonrakiler(p["target"])):
log(f"[{n} {slug}] ret on kontrolu: normal model reddedebilir, adim {p['target']} katmanindan basliyor")
secilen = IDE_PREFIX + p["target"]
if sohbet:
ON_KARAR[sohbet] = (tur, slug, secilen)
while len(ON_KARAR) > ON_KARAR_SINIR:
ON_KARAR.pop(next(iter(ON_KARAR)))
if secilen != slug:
log(f"[{n} {slug}] karar: bu tur {secilen} modeline gidiyor")
req["model"] = secilen
return secilen
def sigan_katmanlar(sira, req, tag):
"""Istegi sigdiramayacak katman zincirden cikar (bos baglam hatasiyla bosa dusmesin)."""
tahmin, sonuc = None, []
for ad in sira:
p = (IDE_BILGI.get(IDE_PREFIX + ad) or {}).get("pencere")
if p:
if tahmin is None:
tahmin = pencere.tahmini_token(req, 2.0)
if tahmin > int(p * 0.90):
log(f"{tag} katman {ad} atlandi: istek sigmaz (tahmini {tahmin} token, pencere {p})")
continue
sonuc.append(ad)
return sonuc
def karar_kurallari_dongusu():
"""Dify kural kaynagi (policy.source = dify) arka planda yenilenir; tur hic beklemez."""
time.sleep(5)
while True:
try:
karar_politikasi.yenile(log)
except Exception as e:
log(f"karar kurallari yenileme hatasi: {type(e).__name__}: {e}")
time.sleep(60)
def catalog_refresh_loop():
"""Kataloglari arka planda yeniler; liste istegini yavaslatmaz."""
minutes = max(1, int(CFG.get("catalog_refresh_min", 5)))
@@ -258,6 +360,7 @@ def catalog_refresh_loop():
log(f"katalog yenileme hatasi: {e}")
threading.Thread(target=catalog_refresh_loop, daemon=True).start()
threading.Thread(target=karar_kurallari_dongusu, daemon=True).start()
# ---- AITURK is hatti: isi gonderebilen AITURK komutu (is_hatti.py). Kurulum sonradan gelebilir: yoksa 2 dk'da bir bakilir.
HAT_KOMUT = [None]
@@ -943,7 +1046,7 @@ class H(http.server.BaseHTTPRequestHandler):
self.send_header("Transfer-Encoding", "chunked")
self.end_headers()
def _denetimli_akis(self, resp, tag, rewriter, izle, istek, sira, ac, hedef=None):
def _denetimli_akis(self, resp, tag, rewriter, izle, istek, sira, ac, hedef=None, devam=None):
"""Katman denetimi (jev_denetim.py): cevap istemciye verilmeden once tamami okunur ve Jev'e sorulur.
Gecerse oldugu gibi verilir; gecmezse atilir ve ayni adimi siradaki katman yapar (ac(ad): (resp, rewriter,
tag)). Son katmanin cevabi sorulmadan akar. Beklerken SSE yorum satiri gider ki Codex akisi kopmus saymasin."""
@@ -955,6 +1058,7 @@ class H(http.server.BaseHTTPRequestHandler):
else:
self._akis_basligi(resp, resp.status)
kalan, son_yazi, son_govde = list(sira), time.time(), b""
devam_kullanildi = False # finishCheck: son cevap ayni modele en fazla bir kez geri verilir
def w(b):
if b:
@@ -999,7 +1103,10 @@ class H(http.server.BaseHTTPRequestHandler):
jev_done, jev_result = jev_threading.Event(), [None]
def denetle():
try:
jev_result[0] = jev_denetim.sor(istek, metin, jev_denetim.anahtar())
if devam is not None and not devam_kullanildi:
jev_result[0] = jev_denetim.sor(istek, metin, jev_denetim.anahtar(), bitis=True)
else:
jev_result[0] = jev_denetim.sor(istek, metin, jev_denetim.anahtar())
finally:
jev_done.set()
jev_threading.Thread(target=denetle, daemon=True).start()
@@ -1011,8 +1118,20 @@ class H(http.server.BaseHTTPRequestHandler):
log(f"{tag} katman denetimi: Jev'e sorulamadi, {'siradaki katman' if gecmedi else 'cevap oldugu gibi geciyor'}")
else:
gecmedi = jev_denetim.karar(puan)
log(f"{tag} katman denetimi: ret={puan['ret']:.2f} sart={puan['sart']:.2f} -> "
log(f"{tag} katman denetimi: ret={puan['ret']:.2f} sart={puan['sart']:.2f}"
+ (f" bitmedi={puan['bitmedi']:.2f}" if 'bitmedi' in puan else "") + " -> "
f"{'GECMEDI' if gecmedi else 'gecti'} ({time.time() - t0:.1f} sn, {len(metin)} karakter{'' if yazi else ', yalniz komut'})")
if not gecmedi and devam is not None and not devam_kullanildi and jev_denetim.bitmedi_mi(puan):
devam_kullanildi = True
try:
yeni_devam = devam(hedef, metin)
except Exception as e:
yeni_devam = None
log(f"{tag} bitis denetimi: devam istegi acilamadi, cevap oldugu gibi geciyor ({type(e).__name__}: {e})")
if yeni_devam is not None:
resp, rewriter, tag = yeni_devam
log(f"{tag} bitis denetimi: is bitmeden bitti denmis, ayni modele bir kez geri verildi")
continue
else:
# Bos adim: ne yazi var ne arac cagrisi (hata olayi da boyledir, or. "upstream 400"). Kabul
# edilmez, siradaki katman yapar; bos yanit cevap diye verilirse Codex adimi bastan dener.
@@ -1547,6 +1666,10 @@ class H(http.server.BaseHTTPRequestHandler):
if hedef and hedef != gelen:
req["model"] = slug = hedef
log(f"[{n} {gelen}] sohbet yonlendirmesi: istek {hedef} modeline gidiyor")
try:
slug = karar_asamasi(n, req, sohbet, slug)
except Exception as e:
log(f"[{n} {slug}] karar asamasi atlandi: {type(e).__name__}: {e}")
mapping = MODEL_MAP.get(slug)
if CFG.get("nokta_karsila", True) and nokta_mesaji_mi(req) and model_gecis.tur_ortasi(req, model_gecis.son_sira(req["input"])):
# Calisan turun icine dusen nokta secimi tasimaz (olcum 10 Ekim 2026) ve cevaplanirsa isi yarida
@@ -1733,14 +1856,19 @@ class H(http.server.BaseHTTPRequestHandler):
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
# Katman denetimi (jev_denetim.py): Sesli-Hizli-Zeki dışında ek modeller Jev'e sorulur; gecmezse adim
# sirayla Katman-1 -> 2 -> 3 -> 4 yapar (secili model zaten katmansa ondan sonrakiler). Katman-4 sorulmadan
# gecer. Katmanlar OmniRoute combolaridir: yedek her zaman AITURK gecidi (ide_route) uzerinden acilir,
# Katman denetimi (jev_denetim.py): muaf listesi disindaki ek modeller Jev'e sorulur; gecmezse adim
# ayardaki zinciri izler (14 Ekim 2026: Katman-1 -> 2 -> 3; secili model zaten katmansa ondan sonrakiler).
# Muaf katman sorulmadan gecer. Katmanlar OmniRoute combolaridir: yedek her zaman AITURK gecidi (ide_route) uzerinden acilir,
# secili model baska bir rotada olsa bile. Gecit yoksa denetim yapilmaz. Ozet isteginde ve aracsiz
# (rewriter yok) yolda kapalidir.
jev_rota = ROUTES.get(ide_route)
jev_etkin = jev_denetim.etkin_mi() and not (ozet_tetik or rewriter is None or jev_rota is None or not jev_denetim.anahtar())
jev_sira = jev_denetim.sonrakiler(target) if jev_etkin else []
if jev_sira:
try:
jev_sira = sigan_katmanlar(jev_sira, req, tag)
except Exception as e:
log(f"{tag} katman pencere kontrolu atlandi: {type(e).__name__}: {e}")
jev_istek = jev_denetim.son_istek(req) if jev_etkin else ""
def jev_ac(ad):
@@ -1753,6 +1881,28 @@ class H(http.server.BaseHTTPRequestHandler):
timeout=UPSTREAM_IDLE_TIMEOUT)
return (yanit, SseRewriter(custom, hedef=ad, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren, tool_names=tool_names),
f"[{n} {slug}=>{ad}]")
def jev_devam(ad, onceki):
"""finishCheck: ayni hedefe, erken biten cevap ve not eklenmis istek. Yalniz AITURK gecidindeki hedefler."""
if ad == target and route_name != ide_route:
raise ValueError("hedef AITURK gecidinde degil")
devam_req = json.loads(data)
devam_req["model"] = ad
devam_req.setdefault("input", []).extend([
{"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": onceki}]},
{"type": "message", "role": "user", "content": [
{"type": "input_text", "text": jev_settings.read()[0]["finishCheck"]["note"]}]}])
jev_hdrs = {"Authorization": f"Bearer {jev_rota['key']}", "Content-Type": "application/json",
"Accept": "text/event-stream", "User-Agent": UA}
yanit = urllib.request.urlopen(urllib.request.Request(
jev_rota["url"], data=json.dumps(devam_req, ensure_ascii=False).encode("utf-8"), headers=jev_hdrs, method="POST"),
timeout=UPSTREAM_IDLE_TIMEOUT)
return (yanit, SseRewriter(custom, hedef=ad, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren, tool_names=tool_names),
f"[{n} {slug}=>{ad} devam]")
try:
jev_bitis = jev_devam if jev_etkin and jev_settings.read()[0]["finishCheck"]["enabled"] else None
except Exception:
jev_bitis = 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.
@@ -1803,7 +1953,7 @@ class H(http.server.BaseHTTPRequestHandler):
time.sleep(min(2 * attempt, 8)); continue
# Only the resolved exact voice target owns this sideband protocol.
# Other models retain their original response/Jev/stream pipeline.
if target == 'Sesli-Hizli-Zeki':
if jev_denetim.izleme_mi(target):
# Capture was taken before rewriting/request dispatch. Only this exact
# route credential may authenticate the returned public receipt.
correlation = self.watch_correlation
@@ -1825,7 +1975,9 @@ class H(http.server.BaseHTTPRequestHandler):
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
return sonuc
if jev_etkin and resp.status == 200:
return self._denetimli_akis(resp, tag, rewriter, izle, jev_istek, jev_sira, jev_ac, hedef=target)
# devam yalniz finishCheck acikken gecer; kapaliyken cagri eskisiyle birebir aynidir.
return self._denetimli_akis(resp, tag, rewriter, izle, jev_istek, jev_sira, jev_ac, hedef=target,
**({"devam": jev_bitis} if jev_bitis else {}))
return self._stream(resp, resp.status, tag, rewriter, izle)
if jev_etkin: # secili modele ulasilamadi: adimi siradaki katman yapar
return self._denetimli_akis(None, tag, rewriter, izle, jev_istek, jev_sira, jev_ac, hedef=target)