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:
1 parent
8f53b090f7
commit
7c4ef18df0
18 files changed
+1672
-119
No files matched your search
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user