fix(codex-bridge): remaining audit findings (routing, trimming, retries, model list fallback, logging)
Second batch from the ten read-only audits; 126 bridge tests. Among them: a spoken number
("opus beş beş") resolves, a used route no longer expires after 7 days, the local reply says how to
go back, an unrouted 9r/ name gets a local 503 instead of going to chatgpt.com, a combo request is not
resent after the upstream closed it, the last model list is served when chatgpt.com is unreachable,
trimming keeps the real last user message, Claude-member combos use the Claude estimate, CRLF SSE is
handled, log lines carry the date and the status endpoint reports a code fingerprint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e2793fa38f
commit
7c0dc467d5
10 files changed
+731
-101
No files matched your search
@@ -32,6 +32,11 @@ import is_hatti
|
||||
|
||||
SURUM = "8"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
try:
|
||||
with open(os.path.abspath(__file__), "rb") as _f:
|
||||
KOD = hashlib.sha256(_f.read()).hexdigest()[:8] # ayni surum numarasi altindaki farkli kodu ayirt eder
|
||||
except OSError:
|
||||
KOD = ""
|
||||
|
||||
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
|
||||
# interpreters the matching `zstandard` build ships next to this file.
|
||||
@@ -97,8 +102,13 @@ def is_timeout(error):
|
||||
"""True for a socket/read timeout, also when urllib wraps it in URLError."""
|
||||
reason = getattr(error, "reason", None)
|
||||
return isinstance(error, TimeoutError) or isinstance(reason, TimeoutError)
|
||||
def kopma_mi(error):
|
||||
"""Baglanti kurulduktan sonra uzak uc kapatti ya da sifirladi (yanit basliklari gelmeden): istek ulasmis ve
|
||||
is baslamis olabilir. Baglanti hic kurulamadiysa (reddedildi, DNS) False."""
|
||||
reason = getattr(error, "reason", None)
|
||||
turler = (ConnectionResetError, ConnectionAbortedError, http.client.IncompleteRead) # RemoteDisconnected dahil
|
||||
return isinstance(error, turler) or isinstance(reason, turler)
|
||||
DEBUG = bool(CFG.get("debug", False))
|
||||
DISPLAY_MAP = CFG.get("display_map", {})
|
||||
UA = CFG.get("user_agent", "AstraBridge/1.0 (codex)")
|
||||
# Bu metinleri iceren hatalar kalicidir; yeniden denemek yerine hemen Codex'e iletilir.
|
||||
NO_RETRY_PATTERNS = CFG.get("no_retry_patterns", ["All fusion panel models failed", "model not found", "does not exist", "insufficient_balance", "allowance exhausted"])
|
||||
@@ -108,7 +118,7 @@ lock = threading.Lock()
|
||||
seq = [0]
|
||||
|
||||
def log(msg):
|
||||
line = time.strftime("%H:%M:%S ") + msg
|
||||
line = time.strftime("%Y-%m-%d %H:%M:%S ") + msg
|
||||
print(line, flush=True)
|
||||
with lock:
|
||||
with open(os.path.join(LOGDIR, "bridge.log"), "a", encoding="utf-8") as f:
|
||||
@@ -202,7 +212,7 @@ def load_ide_catalog(ag=True):
|
||||
"clone_from": clone, "priority": 300 + n, "reasoning_levels": levels,
|
||||
"kind": g["kind"], "context_window": g.get("pencere")})
|
||||
IDE_BILGI[slug] = {"pencere": g.get("pencere"), "gorsel": bool(g.get("gorsel")), "arac": g.get("arac"),
|
||||
"kaynak": g.get("pencere_kaynak") or ""}
|
||||
"kaynak": g.get("pencere_kaynak") or "", "claude": bool(g.get("claude"))}
|
||||
model_map[slug] = (IDE_ROUTE, g["id"], None)
|
||||
n += 1
|
||||
else:
|
||||
@@ -251,7 +261,7 @@ def save_last_models(acct, hdrs, body):
|
||||
yol = _cache_path(acct)
|
||||
veri = {"acct": acct, "headers": hdrs, "at": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"body": body.decode("utf-8")}
|
||||
gecici = yol + ".tmp"
|
||||
gecici = "%s.%d.tmp" % (yol, threading.get_ident()) # ayni hesabin iki es zamanli kaydi carpismasin
|
||||
with open(gecici, "w", encoding="utf-8") as f:
|
||||
json.dump(veri, f, ensure_ascii=False)
|
||||
os.replace(gecici, yol) # yarim dosya birakmamak icin
|
||||
@@ -529,34 +539,47 @@ class SseRewriter:
|
||||
d = {"type": "error", "error": {"message": str(d.get("message")), "type": str(d.get("code") or ""),
|
||||
"status_code": d.get("status_code")}}
|
||||
if t == "error" and isinstance(d.get("error"), dict):
|
||||
# Gecit kalici hatayi (kredi/yetki) "event: error" ile bildirip akisi kapatiyor; Codex bunu tanimaz,
|
||||
# 5 kez yeniden baglanir ve "stream closed" der (10 Ekim 2026: "upstream 402"). Kalici olanlar Codex'in
|
||||
# tanidigi bicime cevrilir; gecici olanlar oldugu gibi birakilir ki Codex yeniden denesin.
|
||||
metin = str(d["error"].get("message") or d["error"].get("type") or "")
|
||||
if pencere.aracsiz_mi(metin):
|
||||
if self.aracsiz is not None:
|
||||
self.aracsiz(self.hedef)
|
||||
return self._gorunur_hata("Bu model araç çağrısını desteklemiyor; Codex'te çalışamaz. Başka bir model seçin.")
|
||||
if self.dolu and pencere.kapali_hata_mi(metin):
|
||||
# Pencereye yakin istekte gecidin nedensiz 400'u: buyuk olasilikla sigmadi. Codex'e sessiz kopma
|
||||
# yerine anlasilir hata gider (ogrenme yapilmaz; neden kesin degil).
|
||||
return [{"type": "response.failed", "response": {
|
||||
"id": "resp_astrabridge_hata", "object": "response", "status": "failed", "output": [],
|
||||
"error": {"code": "context_length_exceeded", "message":
|
||||
"Sohbet seçili modelin bağlam penceresine sığmadı. Sohbeti sıkıştırın ya da daha büyük pencereli bir model seçin."}}}]
|
||||
if pencere.tasma_mi(metin):
|
||||
if self.tasma is not None:
|
||||
self.tasma(self.hedef, metin)
|
||||
return [{"type": "response.failed", "response": {
|
||||
"id": "resp_astrabridge_hata", "object": "response", "status": "failed", "output": [],
|
||||
"error": {"code": "context_length_exceeded", "message": metin[:300]}}}]
|
||||
if re.search(r"(?<![0-9])(401|402|403)(?![0-9])|credit|quota|billing|payment|unauthori[sz]ed|forbidden", metin, re.I):
|
||||
neden = "kredi ya da kota" if re.search(r"402|credit|quota|billing|payment", metin, re.I) else "yetki"
|
||||
return self._gorunur_hata(f"Seçili model şu an kullanılamıyor ({neden} sorunu). Başka bir model seçin. "
|
||||
f"Ayrıntı: {metin[:200]}")
|
||||
if pencere.kapali_hata_mi(metin):
|
||||
return self._gorunur_hata("Seçili model isteği reddetti ve nedenini bildirmedi. Başka bir model seçip "
|
||||
"yeniden deneyin.")
|
||||
cevrilen = self._hata_cevir(d)
|
||||
if cevrilen is not None:
|
||||
# Bekleyen mesajin bitis olayi hatadan SONRA cikmasin: once o birakilir.
|
||||
once = [self._birak("commentary" if self.arac_goruldu else "final_answer")] if self.bekleyen is not None else []
|
||||
return once + cevrilen
|
||||
return self._sirala_normal(d, t)
|
||||
|
||||
def _hata_cevir(self, d):
|
||||
"""Gecidin "event: error" olayi -> Codex'in tanidigi olaylar; None: gecici hata, oldugu gibi kalir."""
|
||||
# Gecit kalici hatayi (kredi/yetki) "event: error" ile bildirip akisi kapatiyor; Codex bunu tanimaz,
|
||||
# 5 kez yeniden baglanir ve "stream closed" der (10 Ekim 2026: "upstream 402"). Kalici olanlar Codex'in
|
||||
# tanidigi bicime cevrilir; gecici olanlar oldugu gibi birakilir ki Codex yeniden denesin.
|
||||
metin = str(d["error"].get("message") or d["error"].get("type") or "")
|
||||
if pencere.aracsiz_mi(metin):
|
||||
if self.aracsiz is not None:
|
||||
self.aracsiz(self.hedef)
|
||||
return self._gorunur_hata("Bu model araç çağrısını desteklemiyor; Codex'te çalışamaz. Başka bir model seçin.")
|
||||
if self.dolu and pencere.kapali_hata_mi(metin):
|
||||
# Pencereye yakin istekte gecidin nedensiz 400'u: buyuk olasilikla sigmadi. Codex'e sessiz kopma
|
||||
# yerine anlasilir hata gider (ogrenme yapilmaz; neden kesin degil).
|
||||
return [{"type": "response.failed", "response": {
|
||||
"id": "resp_astrabridge_hata", "object": "response", "status": "failed", "output": [],
|
||||
"error": {"code": "context_length_exceeded", "message":
|
||||
"Geçit isteği reddetti ve nedenini bildirmedi. Sohbet seçili modelin bağlam penceresine çok yakın; "
|
||||
"sığmamış olabilir. Sohbeti sıkıştırın ya da daha büyük pencereli bir model seçin."}}}]
|
||||
if pencere.tasma_mi(metin):
|
||||
if self.tasma is not None:
|
||||
self.tasma(self.hedef, metin)
|
||||
return [{"type": "response.failed", "response": {
|
||||
"id": "resp_astrabridge_hata", "object": "response", "status": "failed", "output": [],
|
||||
"error": {"code": "context_length_exceeded", "message": metin[:300]}}}]
|
||||
if re.search(r"(?<![0-9])(401|402|403)(?![0-9])|credit|quota|billing|payment|unauthori[sz]ed|forbidden", metin, re.I):
|
||||
neden = "kredi ya da kota" if re.search(r"402|credit|quota|billing|payment", metin, re.I) else "yetki"
|
||||
return self._gorunur_hata(f"Seçili model şu an kullanılamıyor ({neden} sorunu). Başka bir model seçin. "
|
||||
f"Ayrıntı: {metin[:200]}")
|
||||
if pencere.kapali_hata_mi(metin):
|
||||
return self._gorunur_hata("Seçili model isteği reddetti ve nedenini bildirmedi. Başka bir model seçip "
|
||||
"yeniden deneyin.")
|
||||
return None
|
||||
|
||||
def _sirala_normal(self, d, t):
|
||||
item = d.get("item") if isinstance(d.get("item"), dict) else None
|
||||
if item and item.get("type") in ARAC_OGELERI:
|
||||
self.arac_goruldu = True
|
||||
@@ -613,6 +636,8 @@ class SseRewriter:
|
||||
return d
|
||||
def feed(self, chunk):
|
||||
self.buf += chunk
|
||||
if b"\r" in self.buf:
|
||||
self.buf = self.buf.replace(b"\r\n", b"\n") # CRLF ile ayrilan SSE de cevrilir
|
||||
out = b""
|
||||
while b"\n\n" in self.buf:
|
||||
block, self.buf = self.buf.split(b"\n\n", 1)
|
||||
@@ -624,6 +649,8 @@ class SseRewriter:
|
||||
d = json.loads(b"\n".join(lines[i][5:].strip() for i in data_idx))
|
||||
except Exception:
|
||||
out += block + b"\n\n"; continue
|
||||
if not isinstance(d, dict):
|
||||
out += block + b"\n\n"; continue # "data: null" gibi govde akisi kesmez, oldugu gibi gecer
|
||||
d = self._fix(d)
|
||||
if d is None:
|
||||
continue
|
||||
@@ -757,7 +784,10 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
if "chunked" in (self.headers.get("Transfer-Encoding") or "").lower():
|
||||
out = bytearray()
|
||||
while True:
|
||||
line = self.rfile.readline().strip()
|
||||
raw = self.rfile.readline()
|
||||
if not raw:
|
||||
break # baglanti kapandi (EOF): sonsuz donguye girme
|
||||
line = raw.strip()
|
||||
if not line:
|
||||
continue
|
||||
size = int(line.split(b";")[0], 16)
|
||||
@@ -856,7 +886,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
self.command = "GET"
|
||||
if self.command == "GET" and path.rstrip("/").endswith("/status"):
|
||||
durum, _, sayi = (IDE_DURUM[0] or "kapali, 0 model").partition(", ")
|
||||
return self._send_json(200, {"ok": ZSTD_OK, "surum": SURUM, "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]),
|
||||
"ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False))})
|
||||
if self.command == "POST" and path.rstrip("/").endswith("/stop"):
|
||||
@@ -882,7 +912,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return self._control(path)
|
||||
if DEBUG:
|
||||
log(f"[{n} giris {self.command} {path[:70]}] cl={self.headers.get('Content-Length')} enc={self.headers.get('Content-Encoding')} govde={len(body)}")
|
||||
if self.command == "POST" and path.rstrip("/").endswith("/responses"):
|
||||
if self.command == "POST" and path.split("?", 1)[0].rstrip("/").endswith("/responses"):
|
||||
return self._responses(n, path, body)
|
||||
return self._passthrough(n, path, body)
|
||||
|
||||
@@ -900,23 +930,19 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
except urllib.error.HTTPError as e:
|
||||
resp = e; status = e.code
|
||||
except Exception as e:
|
||||
log(f"{tag} HATA {e}"); return self._send_json(502, {"error": {"message": f"bridge passthrough: {e}"}})
|
||||
log(f"{tag} HATA {e}")
|
||||
# Ag yokken de model secici bosalmasin: chatgpt.com'a hic ulasilamazsa son basarili liste verilir.
|
||||
if self.command == "GET" and "/models" in path and self._son_listeyi_ver(tag, self.headers.get("chatgpt-account-id") or "-"):
|
||||
return
|
||||
return self._send_json(502, {"error": {"message": f"bridge passthrough: {e}"}})
|
||||
if self.command == "GET" and "/models" in path:
|
||||
acct = self.headers.get("chatgpt-account-id") or "-"
|
||||
tok = (self.headers.get("Authorization") or "")[-8:]
|
||||
if status == 200:
|
||||
out = self._models(resp, tag)
|
||||
return out
|
||||
body = resp.read()[:300]
|
||||
log(f"{tag} chatgpt.com {status} verdi (hesap={acct} token=..{tok}) govde={body[:160]!r}")
|
||||
cached = LAST_MODELS.get(acct)
|
||||
if cached:
|
||||
self.send_response(200)
|
||||
for k, v in cached["headers"]:
|
||||
self.send_header(k, v)
|
||||
self.send_header("Content-Length", str(len(cached["body"])))
|
||||
self.end_headers(); self.wfile.write(cached["body"])
|
||||
log(f"{tag} son basarili liste verildi ({len(cached['body'])} bayt, {cached['at']})")
|
||||
log(f"{tag} chatgpt.com {status} verdi (hesap={acct}) govde={body[:160]!r}")
|
||||
if self._son_listeyi_ver(tag, acct):
|
||||
return
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(body)))
|
||||
@@ -941,6 +967,19 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"{tag} sesli on hazirlayici baslatilamadi: {type(e).__name__}")
|
||||
self._stream(resp, status, tag, rewriter, izle)
|
||||
|
||||
def _son_listeyi_ver(self, tag, acct):
|
||||
"""Bu hesabin son basarili model listesini gonderir. True: gonderildi."""
|
||||
cached = LAST_MODELS.get(acct)
|
||||
if not cached:
|
||||
return False
|
||||
self.send_response(200)
|
||||
for k, v in cached["headers"]:
|
||||
self.send_header(k, v)
|
||||
self.send_header("Content-Length", str(len(cached["body"])))
|
||||
self.end_headers(); self.wfile.write(cached["body"])
|
||||
log(f"{tag} son basarili liste verildi ({len(cached['body'])} bayt, {cached['at']})")
|
||||
return True
|
||||
|
||||
def _openai_ozet_coz(self, n, oge, ozet_cfg):
|
||||
"""OpenAI'nin sifreli ozetini bir kez OpenAI'ye duz metne dokturur; sonucu diske onbellekler (icerik gunluge yazilmaz)."""
|
||||
klasor = os.path.join(LOGDIR, "ozet-onbellek")
|
||||
@@ -1006,12 +1045,11 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
data = json.loads(raw)
|
||||
models = data.get("models", [])
|
||||
changed = 0
|
||||
for m in models:
|
||||
new = DISPLAY_MAP.get(m.get("slug"))
|
||||
if new:
|
||||
m["display_name"] = new; changed += 1
|
||||
# Ozgun abonelik modellerinin adi, sirasi ve yeri degistirilmez (eski "display_map" ayari artik okunmaz).
|
||||
by_slug = {m.get("slug"): m for m in models}
|
||||
for x in EXTRA_MODELS:
|
||||
ozgun_kimlikler = set(by_slug)
|
||||
ekler = EXTRA_MODELS # tek kopya: liste hazirlanirken katalog yenilenirse sira ve icerik tutarli kalir
|
||||
for x in ekler:
|
||||
if x["slug"] in by_slug:
|
||||
continue
|
||||
base = by_slug.get(x.get("clone_from") or "gpt-6-astra") or (models[0] if models else {})
|
||||
@@ -1051,13 +1089,14 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
if not m.get("base_instructions") and not has_tmpl:
|
||||
m.update(saved)
|
||||
models.append(m); changed += 1
|
||||
if EXTRA_MODELS:
|
||||
ide_catalog.sirala_codex(models, EXTRA_MODELS)
|
||||
if ekler:
|
||||
ide_catalog.sirala_codex(models, ekler, ozgun_kimlikler)
|
||||
out = json.dumps(data, ensure_ascii=False).encode("utf-8")
|
||||
except Exception as e:
|
||||
log(f"{tag} model listesi degistirilemedi: {e}")
|
||||
out = raw; changed = 0
|
||||
raw_len = len(out)
|
||||
duz = out # sikistirilmamis govde: yedege bu yazilir
|
||||
catalog_etag = 'W/"astrabridge-' + hashlib.sha256(out).hexdigest()[:24] + '"'
|
||||
enc_out = None
|
||||
if "gzip" in (self.headers.get("Accept-Encoding") or "").lower() and changed:
|
||||
@@ -1070,15 +1109,16 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
if kl in ("etag", "x-models-etag") and changed:
|
||||
v = catalog_etag
|
||||
hdrs_out.append((k, v))
|
||||
if enc_out:
|
||||
hdrs_out.append(("Content-Encoding", enc_out))
|
||||
if changed and not enc_out:
|
||||
if changed:
|
||||
# Yedek her zaman sikistirilmamis govdeyle tutulur (istemci gzip istese de).
|
||||
acct_ = self.headers.get("chatgpt-account-id") or "-"
|
||||
onceki = LAST_MODELS.get(acct_)
|
||||
LAST_MODELS[acct_] = {"headers": hdrs_out, "body": out, "at": time.strftime("%H:%M:%S")}
|
||||
LAST_MODELS[acct_] = {"headers": list(hdrs_out), "body": duz, "at": time.strftime("%Y-%m-%d %H:%M:%S")}
|
||||
# Liste 4 dakikada bir isteniyor; sadece icerik degistiyse diske yaz.
|
||||
if not onceki or onceki.get("body") != out:
|
||||
save_last_models(acct_, hdrs_out, out)
|
||||
if not onceki or onceki.get("body") != duz:
|
||||
save_last_models(acct_, list(hdrs_out), duz)
|
||||
if enc_out:
|
||||
hdrs_out.append(("Content-Encoding", enc_out))
|
||||
self.send_response(200)
|
||||
for k, v in hdrs_out:
|
||||
self.send_header(k, v)
|
||||
@@ -1120,6 +1160,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return soru
|
||||
model_gecis.metni_degistir(req, sira, "[Köprü notu: kullanıcı model değiştirmek istedi ama ad belirsizdi; "
|
||||
"model değişmedi. İşine devam et; cevabının sonunda şunu ilet: " + soru + "]")
|
||||
self.komut_govdeyi_degistirdi = True
|
||||
return None
|
||||
hedef = sonuc["slug"]
|
||||
if hedef.startswith("ide/") and MODEL_MAP.get(hedef) is None and "aiturk-ide" not in ROUTES:
|
||||
@@ -1131,9 +1172,12 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return None if ortada else f"Sohbetin modeli zaten {sonuc['ad']}."
|
||||
if ortada:
|
||||
model_gecis.metni_degistir(req, sira, model_gecis.devir_notu(sonuc["ad"], model_adi(etkin)))
|
||||
self.komut_govdeyi_degistirdi = True
|
||||
return None
|
||||
# Listede ayni modeli yeniden secmek yonlendirmeyi kaldirmaz (Codex ayni adi gonderir); geri donus yolu soylenir.
|
||||
donus = f" Listede seçili modele dönmek için \"modeli {model_adi(gelen)} yap\" de." if hedef != gelen else ""
|
||||
return (f"Sohbetin modeli artık {sonuc['ad']}. Önceki model {model_adi(etkin)} idi. "
|
||||
f"Bundan sonraki işleri {sonuc['ad']} yapacak; hangi model diye sorulursa bunu söyle.")
|
||||
f"Bundan sonraki işleri {sonuc['ad']} yapacak; hangi model diye sorulursa bunu söyle.{donus}")
|
||||
|
||||
def _responses(self, n, path, body):
|
||||
try:
|
||||
@@ -1147,6 +1191,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
once = len(req.get("input") or []), json.dumps((req.get("input") or [None])[-3:], ensure_ascii=False, sort_keys=True)
|
||||
sohbet = str(req.get("prompt_cache_key") or "")
|
||||
if CFG.get("sesle_model", True) and sohbet:
|
||||
self.komut_govdeyi_degistirdi = False
|
||||
try:
|
||||
yerel = self._model_komutu(n, req, sohbet, gelen)
|
||||
except Exception as e:
|
||||
@@ -1154,8 +1199,10 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"[{n} {gelen}] model komutu atlandi: {type(e).__name__}: {e}")
|
||||
if yerel is not None:
|
||||
return self._yerel_yanit(gelen, yerel)
|
||||
if self.komut_govdeyi_degistirdi:
|
||||
govde_degisti = True # komut mesaji nota cevrildi: abonelik yolunda da degismis govde gider
|
||||
try:
|
||||
if model_gecis.gecmisi_temizle(req, bilinen_modeller()):
|
||||
if model_gecis.gecmisi_temizle(req, bilinen_modeller(), YONLENDIRME.al(sohbet, gelen) or gelen):
|
||||
govde_degisti = True
|
||||
except Exception as e:
|
||||
log(f"[{n} {gelen}] gecmis komut temizligi atlandi: {type(e).__name__}: {e}")
|
||||
@@ -1176,7 +1223,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
# Model degisimini uygulamak icin yazilan tek "." modele gonderilmez: hicbir model onu onay ya da
|
||||
# istek sanip is yapmaz, yavas modelde asil soru beklemez. Secilen modelin adiyla aninda donulur.
|
||||
log(f"[{n} {slug}] nokta mesaji kopruden karsilandi (modele gonderilmedi)")
|
||||
return self._yerel_yanit(gelen, f"{model_adi(slug)} hazır. Sohbetin modeli artık {model_adi(slug)}.")
|
||||
return self._yerel_yanit(gelen, f"{model_adi(slug)} seçildi. Sohbetin modeli artık {model_adi(slug)}.")
|
||||
# Is hatti talimati yalniz masaustu uygulamasindan gelen istege eklenir; CLI hic gormez (is_hatti.py).
|
||||
kaynak = self.headers.get("originator") or ""
|
||||
if kaynak and kaynak not in GORULEN_ISTEMCI:
|
||||
@@ -1206,10 +1253,15 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
ozet_acik = ozet_cfg.get("enabled", False)
|
||||
catalog = CFG.get("nine_router_catalog") or {}
|
||||
prefix = catalog.get("prefix", "9r/")
|
||||
if mapping is None and catalog.get("enabled") and slug.startswith(prefix):
|
||||
# Newly added API models can be selected before the background refresh.
|
||||
# Provider-qualified IDs are passed intact to 9Router, never to ChatGPT.
|
||||
mapping = (catalog.get("route", "9router"), slug[len(prefix):], None)
|
||||
if mapping is None and prefix and slug.startswith(prefix):
|
||||
if catalog.get("enabled") and catalog.get("route", "9router") in ROUTES:
|
||||
# Newly added API models can be selected before the background refresh.
|
||||
# Provider-qualified IDs are passed intact to 9Router, never to ChatGPT.
|
||||
mapping = (catalog.get("route", "9router"), slug[len(prefix):], None)
|
||||
else:
|
||||
# Rotasi kalmamis eski ad chatgpt.com'a baska bir adla gonderilmez ("model desteklenmiyor" donuyordu).
|
||||
log(f"[{n} {slug}] bu on ekin rotasi yok; istek gonderilmedi")
|
||||
return self._send_json(503, {"error": {"message": "Bu model artik bu koprude tanimli degil; listeden baska bir model secin."}})
|
||||
ide_prefix, ide_route = "ide/", "aiturk-ide" # load_ide_catalog ile ayni degerler
|
||||
if mapping is None and slug.startswith(ide_prefix) and ide_route in ROUTES:
|
||||
mapping = (ide_route, slug[len(ide_prefix):], None)
|
||||
@@ -1287,7 +1339,11 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return
|
||||
# 9Router cevirisinden gecen hedefler (9Router ve AITURK IDE gecidi): ayni istek duzeltmeleri uygulanir.
|
||||
via_router = route_name in (catalog.get("route", "9router"), ide_route)
|
||||
route = ROUTES[route_name]
|
||||
route = ROUTES.get(route_name)
|
||||
if route is None:
|
||||
# Katalog yenilemesi rotayi bu istek hazirlanirken kaldirdi (baglama kapandi ya da IDE oturumu gitti).
|
||||
log(f"[{n} {slug}] rota artik yok: {route_name}")
|
||||
return self._send_json(503, {"error": {"message": "Bu modelin baglantisi su an kapali; birazdan yeniden deneyin ya da baska bir model secin."}})
|
||||
tag = f"[{n} {route_name.upper()} {slug}->{target}]"
|
||||
req["model"] = target
|
||||
req.pop("client_metadata", None)
|
||||
@@ -1302,6 +1358,12 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
if ozet_tetik:
|
||||
ozet_kopru.ozet_istegi_hazirla(req, ozet_cfg.get("istem"))
|
||||
log(f"{tag} ozet kopru: sikistirma istegi duz metin ozet istegine cevrildi (girdi={len(req.get('input', []))} oge)")
|
||||
if via_router and CFG.get('resim_sadelestir', True):
|
||||
# Uretilen resim (≈3 MB) 9Router cevirisinde duz yaziya donup sohbeti 3M token'a cikariyordu (24 Eylul 2026).
|
||||
# Duzlestirmeden ONCE: kimliksiz arac ciktisi orada duz metne cevrilir ve icindeki resim taninmaz olur.
|
||||
cikan, kazanc = resimleri_sadelestir(req, gorsel=bool((IDE_BILGI.get(slug) or {}).get("gorsel")))
|
||||
if cikan:
|
||||
log(f"{tag} {cikan} resim not ile degistirildi ({kazanc // 1024} KB)")
|
||||
rewriter = None
|
||||
if flat if flat is not None else route.get("flatten_tools"):
|
||||
custom = flatten_request(req)
|
||||
@@ -1310,11 +1372,6 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"{tag} {duzelen} aracin semasi duzeltildi (ust duzey oneOf/anyOf/allOf)")
|
||||
rewriter = SseRewriter(custom, hedef=target, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren)
|
||||
fix_tool_ids(req)
|
||||
if via_router and CFG.get('resim_sadelestir', True):
|
||||
# Uretilen resim (≈3 MB) 9Router cevirisinde duz yaziya donup sohbeti 3M token'a cikariyordu (24 Eylul 2026).
|
||||
cikan, kazanc = resimleri_sadelestir(req, gorsel=bool((IDE_BILGI.get(slug) or {}).get("gorsel")))
|
||||
if cikan:
|
||||
log(f"{tag} {cikan} resim not ile degistirildi ({kazanc // 1024} KB)")
|
||||
if route_name == catalog.get('route', '9router') and target.startswith('ag/gemini-'):
|
||||
merged = coalesce_context(req)
|
||||
if merged:
|
||||
@@ -1332,9 +1389,10 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"{tag} girdi kullanici mesajiyla bitmiyordu ({sonlar}), devam mesaji eklendi")
|
||||
hedef_pencere = (IDE_BILGI.get(slug) or {}).get("pencere")
|
||||
# Penceresi bilinmeyen (varsayilan degerli) modelde bekci calismaz: tahmine dayanarak gecmis kirpilmaz.
|
||||
pencere_biliniyor = "varsayilan" not in ((IDE_BILGI.get(slug) or {}).get("kaynak") or "varsayilan")
|
||||
# Comboda tek bilinmeyen uye, bilinen kucuk uyenin sinirini gecersiz kilmaz (pencere.bekci_calisir).
|
||||
pencere_biliniyor = bool(hedef_pencere) and pencere.bekci_calisir((IDE_BILGI.get(slug) or {}).get("kaynak"), hedef_pencere)
|
||||
if hedef_pencere and pencere_biliniyor and CFG.get("tasma_bekcisi", True) and not ozet_tetik:
|
||||
kirpilan, sigdi, tahmin = pencere.sigdir(req, hedef_pencere, pencere.oran_sec(target))
|
||||
kirpilan, sigdi, tahmin = pencere.sigdir(req, hedef_pencere, pencere.oran_sec(target, bool((IDE_BILGI.get(slug) or {}).get("claude"))))
|
||||
if rewriter is not None:
|
||||
rewriter.dolu = tahmin >= hedef_pencere * 0.7
|
||||
if kirpilan:
|
||||
@@ -1359,8 +1417,8 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
try:
|
||||
resp = urllib.request.urlopen(r, timeout=UPSTREAM_IDLE_TIMEOUT)
|
||||
except urllib.error.HTTPError as e:
|
||||
errbody = e.read()[:400]
|
||||
last = (e.code, errbody)
|
||||
errbody = e.read() # istemciye TAM govde gider (400 bayta kesilince JSON bozuluyordu); gunlukte kisaltilir
|
||||
last = (e.code, errbody[:400])
|
||||
etxt = errbody.decode("utf-8", "replace")
|
||||
# 9Router kalici ust-kaynak hatalarini (400/403 ...) 503 govdesine sararak
|
||||
# donduruyor. Bunlari tekrar denemek dakikalarca bosuna bekletiyordu.
|
||||
@@ -1381,6 +1439,9 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return self._send_sse_failed("context_length_exceeded", etxt[:300])
|
||||
self.send_response(e.code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
bekle = e.headers.get("Retry-After") if e.headers is not None else None
|
||||
if bekle:
|
||||
self.send_header("Retry-After", str(bekle)) # 429'da ne kadar beklenecegi istemciye ulassin
|
||||
self.send_header("Content-Length", str(len(errbody)))
|
||||
self.end_headers(); self.wfile.write(errbody); return
|
||||
except Exception as e:
|
||||
@@ -1388,6 +1449,9 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
if combo and is_timeout(e):
|
||||
log(f"{tag} combo zaman asimi, tekrar gonderilmiyor (calisiyor olabilir): {e}")
|
||||
break
|
||||
if combo and kopma_mi(e):
|
||||
log(f"{tag} combo istegi gonderildikten sonra baglanti koptu, tekrar gonderilmiyor (calisiyor olabilir): {e}")
|
||||
break
|
||||
log(f"{tag} deneme {attempt}: {e}")
|
||||
time.sleep(min(2 * attempt, 8)); continue
|
||||
# Headers go to the client from here on: a cut stream is never resent.
|
||||
|
||||
Reference in new issue
Block a user