feat(codex-link): bridge 7 - external models work like subscription models
Measured with the unmodified Codex binary on 10 Oct 2026: - real context windows per model (NanoGPT list, local table, combo = its smallest member, capped at the summarizer's window) so Codex compacts by itself when switching to a smaller model; an overflow guard trims oversized old parts and reports what still does not fit - classic tools instead of code mode for external models: GLM 0/3 -> 3/3, Qwen 0/3 -> 2/3 on simple tool tasks, Claude/cx/Gemini unchanged - phase (commentary / final_answer) on external assistant messages - an answer cut by the client is carried to the next turn once - images stay for vision-capable targets - summary brake stops tight loops, not frequent compaction - permanent gateway errors and tool-less targets fail at once with a readable message instead of five reconnects - a lone "." (used to apply a model change in voice) is answered locally - the bridge listens within seconds; the IDE waits 45 s for it Release: AITURK IDE 1.0.0-beta.26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac0fdcf355
commit
acf88f8314
19 files changed
+1160
-47
No files matched your search
@@ -25,8 +25,10 @@ except ImportError:
|
||||
from resim_context import resimleri_sadelestir
|
||||
import ide_catalog
|
||||
import ozet_kopru
|
||||
import pencere
|
||||
import yarim
|
||||
|
||||
SURUM = "6"
|
||||
SURUM = "7"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
|
||||
@@ -165,13 +167,14 @@ load_9router_catalog()
|
||||
IDE_PREFIX = "ide/"
|
||||
IDE_ROUTE = "aiturk-ide"
|
||||
IDE_DURUM = [None]
|
||||
IDE_BILGI = {} # slug -> {"pencere", "gorsel", "arac"} (pencere.coz)
|
||||
|
||||
def load_ide_catalog():
|
||||
def load_ide_catalog(ag=True):
|
||||
"""IDE'nin gecit katalogunu "ide/<id>" olarak ekler. Ayar kapaliysa ya da IDE anahtari
|
||||
yoksa girdiler kaldirilir; gecit gecici olarak cevap vermezse son liste korunur."""
|
||||
global MODEL_MAP, EXTRA_MODELS
|
||||
try:
|
||||
durum, base, anahtar, girdiler = ide_catalog.yukle(UA)
|
||||
durum, base, anahtar, girdiler = ide_catalog.yukle(UA, home=HOME, ag=ag)
|
||||
except Exception as e:
|
||||
log(f"AITURK IDE katalogu alinamadi, son liste korunuyor: {e}")
|
||||
return
|
||||
@@ -195,7 +198,9 @@ def load_ide_catalog():
|
||||
"description": g.get("aciklama") or f"AITURK IDE: {g['ad']}",
|
||||
"route": IDE_ROUTE, "target": g["id"],
|
||||
"clone_from": clone, "priority": 300 + n, "reasoning_levels": levels,
|
||||
"kind": g["kind"]})
|
||||
"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 ""}
|
||||
model_map[slug] = (IDE_ROUTE, g["id"], None)
|
||||
n += 1
|
||||
else:
|
||||
@@ -207,11 +212,18 @@ def load_ide_catalog():
|
||||
IDE_DURUM[0] = ozet
|
||||
log(f"AITURK IDE Codex Baglama: {ozet}")
|
||||
|
||||
load_ide_catalog()
|
||||
# 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 catalog_refresh_loop():
|
||||
"""Kataloglari arka planda yeniler; liste istegini yavaslatmaz."""
|
||||
minutes = max(1, int(CFG.get("catalog_refresh_min", 5)))
|
||||
time.sleep(3)
|
||||
try:
|
||||
load_ide_catalog() # acilistan hemen sonra: guncel pencere verisiyle
|
||||
except Exception as e:
|
||||
log(f"katalog yenileme hatasi: {e}")
|
||||
while True:
|
||||
time.sleep(minutes * 60)
|
||||
for yukle in (load_9router_catalog, load_ide_catalog):
|
||||
@@ -410,12 +422,96 @@ def drop_foreign_items(req):
|
||||
return atilan
|
||||
|
||||
|
||||
ARAC_OGELERI = {"function_call", "custom_tool_call", "local_shell_call", "tool_search_call", "web_search_call",
|
||||
"mcp_call", "computer_call", "image_generation_call", "code_interpreter_call"}
|
||||
|
||||
def asistan_mesaji_mi(item):
|
||||
return isinstance(item, dict) and item.get("type") == "message" and item.get("role") == "assistant"
|
||||
|
||||
def phase_damgala(cikti):
|
||||
"""Bitmis yanitin cikti listesinde phase'i olmayan asistan mesajlarini damgalar. Kural: yanitta arac
|
||||
cagrisi varsa tur suruyor demektir, butun mesajlar ara aciklamadir; yoksa yalniz SON mesaj son cevaptir.
|
||||
Codex masaustu ara mesaji bu isaretle tanir; isaretsiz her mesaji son cevap sayar (10 Ekim 2026)."""
|
||||
arac_var = any(isinstance(o, dict) and o.get("type") in ARAC_OGELERI for o in cikti)
|
||||
mesajlar = [o for o in cikti if asistan_mesaji_mi(o)]
|
||||
for o in mesajlar:
|
||||
if not o.get("phase"):
|
||||
o["phase"] = "commentary" if (arac_var or o is not mesajlar[-1]) else "final_answer"
|
||||
|
||||
class SseRewriter:
|
||||
"""function_call -> custom_tool_call donusumu (custom arac adlari icin), satir satir SSE."""
|
||||
def __init__(self, custom):
|
||||
"""function_call -> custom_tool_call donusumu (custom arac adlari icin), satir satir SSE.
|
||||
Ayrica dis modelin asistan mesajlarina phase damgasi koyar: mesajin "bitti" olayi, arkasindan ne
|
||||
geldigi belli olana kadar bekletilir (arac cagrisi ya da baska oge -> commentary; yanit bitti ->
|
||||
final_answer). Metin parcalari (delta) bekletilmez, canli akar."""
|
||||
def __init__(self, custom, hedef=None, tasma=None, aracsiz=None):
|
||||
self.custom = custom
|
||||
self.buf = b""
|
||||
self.custom_ids = set()
|
||||
self.bekleyen = None # phase karari bekleyen "output_item.done" olayi
|
||||
self.arac_goruldu = False
|
||||
self.hedef = hedef # gecit model kimligi (tasma siniri ogrenmek icin)
|
||||
self.tasma = tasma # callable(hedef, hata metni): sinir ogrenildiyse cagrilir
|
||||
self.aracsiz = aracsiz # callable(hedef): hedef "arac cagrisi desteklenmiyor" dedi
|
||||
def _birak(self, phase):
|
||||
d, self.bekleyen = self.bekleyen, None
|
||||
if d is not None and not d["item"].get("phase"):
|
||||
d["item"]["phase"] = phase
|
||||
return d
|
||||
def _sirala(self, d):
|
||||
"""Bir olay -> gonderilecek olaylar listesi (bekleyen mesaj dogru phase ile one alinir)."""
|
||||
t = d.get("type", "")
|
||||
if t == "error" and not isinstance(d.get("error"), dict) and d.get("message"):
|
||||
# Gecidin duz bicimi: {"type":"error","code":"bad_request","message":"[400]: ...","status_code":400}
|
||||
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 [{"type": "response.failed", "response": {
|
||||
"id": "resp_astrabridge_hata", "object": "response", "status": "failed", "output": [],
|
||||
"error": {"code": "invalid_request_error", "message":
|
||||
"Bu model araç çağrısını desteklemiyor; Codex'te çalışamaz. Başka 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):
|
||||
kod = "insufficient_quota" if re.search(r"402|credit|quota|billing|payment", metin, re.I) else "invalid_api_key"
|
||||
d = {"type": "response.failed", "response": {
|
||||
"id": "resp_astrabridge_hata", "object": "response", "status": "failed", "output": [],
|
||||
"error": {"code": kod, "message": "AITURK ağ geçidi isteği reddetti: " + metin[:300]}}}
|
||||
t = "response.failed"
|
||||
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
|
||||
once = []
|
||||
if self.bekleyen is not None and (t == "response.output_item.added"
|
||||
or (t == "response.output_item.done" and item is not None)):
|
||||
once.append(self._birak("commentary"))
|
||||
if t in ("response.completed", "response.incomplete", "response.failed") and self.bekleyen is not None:
|
||||
once.append(self._birak("commentary" if self.arac_goruldu else "final_answer"))
|
||||
if t == "response.completed" and isinstance((d.get("response") or {}).get("output"), list):
|
||||
phase_damgala(d["response"]["output"])
|
||||
if t == "response.failed" and self.tasma is not None:
|
||||
hata = (d.get("response") or {}).get("error") or {}
|
||||
metin = str(hata.get("message") or "")
|
||||
if pencere.tasma_mi(metin) or pencere.tasma_mi(str(hata.get("code") or "")):
|
||||
hata["code"] = "context_length_exceeded" # Codex'in tanidigi tasma kodu
|
||||
self.tasma(self.hedef, metin)
|
||||
if t == "response.output_item.done" and asistan_mesaji_mi(item) and not item.get("phase"):
|
||||
if self.arac_goruldu:
|
||||
item["phase"] = "commentary"
|
||||
else:
|
||||
self.bekleyen = d
|
||||
return once
|
||||
return once + [d]
|
||||
def _fix(self, d):
|
||||
t = d.get("type", "")
|
||||
item = d.get("item")
|
||||
@@ -463,16 +559,63 @@ class SseRewriter:
|
||||
if d is None:
|
||||
continue
|
||||
ev = [l for l in lines if l.startswith(b"event:")]
|
||||
evline = (b"event: " + d.get("type", "").encode() + b"\n") if ev else b""
|
||||
out += evline + b"data: " + json.dumps(d, ensure_ascii=False).encode("utf-8") + b"\n\n"
|
||||
for o in self._sirala(d):
|
||||
evline = (b"event: " + o.get("type", "").encode() + b"\n") if ev else b""
|
||||
out += evline + b"data: " + json.dumps(o, ensure_ascii=False).encode("utf-8") + b"\n\n"
|
||||
return out
|
||||
def flush(self):
|
||||
b, self.buf = self.buf, b""
|
||||
if self.bekleyen is not None:
|
||||
# Akis bitis olayi gelmeden koptu: bekleyen mesaj yutulmaz, isaretsiz haliyle iletilir.
|
||||
o, self.bekleyen = self.bekleyen, None
|
||||
b = (b"event: " + o.get("type", "").encode() + b"\ndata: "
|
||||
+ json.dumps(o, ensure_ascii=False).encode("utf-8") + b"\n\n") + b
|
||||
return b
|
||||
|
||||
RESP_HOP = {"host", "content-length", "accept-encoding", "transfer-encoding", "connection", "content-encoding"}
|
||||
REQ_HOP = {"host", "content-length", "accept-encoding", "transfer-encoding", "connection"}
|
||||
|
||||
NOKTA_ISARETLERI = {".", "。"}
|
||||
|
||||
def nokta_mesaji_mi(req):
|
||||
"""Son girdi, metni yalnizca "." olan bir kullanici mesaji mi? Kullanici sesli konusmada model degistirdikten
|
||||
sonra secimin uygulanmasi icin tek bir "." yaziyor (10 Ekim 2026). Bu mesaj bir istek degildir."""
|
||||
girdi = req.get("input")
|
||||
if not isinstance(girdi, list) or not girdi:
|
||||
return False
|
||||
son = girdi[-1]
|
||||
if not (isinstance(son, dict) and son.get("type", "message") == "message" and son.get("role") == "user"):
|
||||
return False
|
||||
icerik = son.get("content")
|
||||
if isinstance(icerik, str):
|
||||
metin = icerik
|
||||
elif isinstance(icerik, list) and all(isinstance(p, dict) and p.get("type") in ("input_text", "text") for p in icerik):
|
||||
metin = "".join(str(p.get("text") or "") for p in icerik)
|
||||
else:
|
||||
return False # resim, dosya vb. tasiyan mesaj nokta mesaji sayilmaz
|
||||
return metin.strip() in NOKTA_ISARETLERI
|
||||
|
||||
def hazir_yaniti(slug, metin):
|
||||
"""Modele gitmeden uretilen, Codex'in tam bir yanit olarak kabul ettigi kisa akis."""
|
||||
damga = "%d" % int(time.time() * 1000)
|
||||
rid, mid = "resp_astrabridge_" + damga, "msg_astrabridge_" + damga
|
||||
oge = {"id": mid, "type": "message", "role": "assistant", "status": "completed", "phase": "final_answer",
|
||||
"content": [{"type": "output_text", "text": metin, "annotations": []}]}
|
||||
kullanim = {"input_tokens": 0, "input_tokens_details": {"cached_tokens": 0}, "output_tokens": 0,
|
||||
"output_tokens_details": {"reasoning_tokens": 0}, "total_tokens": 0}
|
||||
olaylar = [
|
||||
{"type": "response.created", "response": {"id": rid, "object": "response", "status": "in_progress",
|
||||
"model": slug, "output": []}},
|
||||
{"type": "response.output_item.added", "output_index": 0,
|
||||
"item": {"id": mid, "type": "message", "role": "assistant", "status": "in_progress", "content": []}},
|
||||
{"type": "response.output_text.delta", "item_id": mid, "output_index": 0, "content_index": 0, "delta": metin},
|
||||
{"type": "response.output_item.done", "output_index": 0, "item": oge},
|
||||
{"type": "response.completed", "response": {"id": rid, "object": "response", "status": "completed",
|
||||
"model": slug, "output": [oge], "usage": kullanim}},
|
||||
]
|
||||
return b"".join(b"event: " + o["type"].encode() + b"\ndata: " + json.dumps(o, ensure_ascii=False).encode("utf-8")
|
||||
+ b"\n\n" for o in olaylar)
|
||||
|
||||
def claude_sonu_duzelt(req):
|
||||
"""9Router Claude cevirisi developer/system mesajlarini dusurur; geriye kalan son oge asistan mesaji ya da
|
||||
reasoning ise Claude 'assistant message prefill' hatasi (400) verir. O durumda sona kisa user mesaji eklenir.
|
||||
@@ -531,7 +674,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
n = int(self.headers.get("Content-Length") or 0)
|
||||
return self.rfile.read(n) if n else b""
|
||||
|
||||
def _stream(self, resp, status, tag, rewriter=None):
|
||||
def _stream(self, resp, status, tag, rewriter=None, izle=None):
|
||||
self.send_response(status)
|
||||
for k, v in resp.headers.items():
|
||||
if k.lower() in RESP_HOP: continue
|
||||
@@ -539,6 +682,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.end_headers()
|
||||
total = 0
|
||||
bas = b"" # ust kaynagin ilk baytlari: cok kisa (bos) akislarin nedeni gunluge yazilir
|
||||
def w(b):
|
||||
if b:
|
||||
self.wfile.write(b"%x\r\n" % len(b) + b + b"\r\n"); self.wfile.flush()
|
||||
@@ -547,12 +691,44 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
chunk = resp.read(4096)
|
||||
if not chunk: break
|
||||
total += len(chunk)
|
||||
if len(bas) < 600:
|
||||
bas += chunk[:600 - len(bas)]
|
||||
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 ({total} bayt)")
|
||||
# Yanit bitmeden istemci kapatti (durdurma ya da uygulama kapanmasi): yarim metin sonraki tura tasinir.
|
||||
if izle is not None and yarim.kaydet(izle):
|
||||
log(f"{tag} yarim cevap saklandi ({len(izle.metin)} karakter); ayni sohbetin sonraki istegine not olarak eklenecek")
|
||||
log(f"{tag} bitti status={status} {total} bayt")
|
||||
if rewriter is not None and total < 600:
|
||||
log(f"{tag} kisa akis govdesi: {bas[:500]!r}")
|
||||
|
||||
def _tasma_ogren(self, hedef, metin):
|
||||
sinir = pencere.sinir_oku(metin)
|
||||
if sinir and pencere.ogren(HOME, hedef, sinir):
|
||||
log(f"pencere ogrenildi: {hedef} gercek siniri {sinir} token; model listesi yenileniyor")
|
||||
threading.Thread(target=load_ide_catalog, daemon=True).start()
|
||||
|
||||
def _aracsiz_ogren(self, hedef):
|
||||
if pencere.ogren_aracsiz(HOME, hedef):
|
||||
log(f"aracsiz model ogrenildi: {hedef}; model listesinde isaretlenecek")
|
||||
threading.Thread(target=load_ide_catalog, daemon=True).start()
|
||||
|
||||
def _send_sse_failed(self, kod, mesaj):
|
||||
"""Codex'in akis icinde tanidigi hata bicimi (response.failed + error.code)."""
|
||||
kimlik = "resp_astrabridge_%d" % int(time.time() * 1000)
|
||||
olay = {"type": "response.failed", "response": {"id": kimlik, "object": "response", "status": "failed",
|
||||
"error": {"code": kod, "message": mesaj}, "output": []}}
|
||||
govde = b"event: response.failed\ndata: " + json.dumps(olay, ensure_ascii=False).encode("utf-8") + b"\n\n"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.send_header("Content-Length", str(len(govde)))
|
||||
self.end_headers()
|
||||
self.wfile.write(govde)
|
||||
|
||||
def _send_json(self, status, obj):
|
||||
b = json.dumps(obj).encode()
|
||||
@@ -605,7 +781,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return self._responses(n, path, body)
|
||||
return self._passthrough(n, path, body)
|
||||
|
||||
def _passthrough(self, n, path, body, tagname="pass", rewritten=False, rewriter=None):
|
||||
def _passthrough(self, n, path, body, tagname="pass", rewritten=False, rewriter=None, izle=None):
|
||||
tag = f"[{n} {tagname} {self.command} {path[:80]}]"
|
||||
hdrs = {k: v for k, v in self.headers.items() if k.lower() not in REQ_HOP}
|
||||
if rewritten:
|
||||
@@ -658,7 +834,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"{tag} sesli on hazirlayici baslatildi")
|
||||
except Exception as e:
|
||||
log(f"{tag} sesli on hazirlayici baslatilamadi: {type(e).__name__}")
|
||||
self._stream(resp, status, tag, rewriter)
|
||||
self._stream(resp, status, tag, rewriter, izle)
|
||||
|
||||
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)."""
|
||||
@@ -741,7 +917,18 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
m["visibility"] = "list"
|
||||
m["priority"] = x.get("priority", 0)
|
||||
m["upgrade"] = None; m["availability_nux"] = None
|
||||
m["additional_speed_tiers"] = []; m["service_tiers"] = []
|
||||
dis = x["slug"].startswith(IDE_PREFIX)
|
||||
if not dis:
|
||||
m["additional_speed_tiers"] = []; m["service_tiers"] = []
|
||||
# Dis modelde hiz katmani ilan edilmeye devam eder (yoksa Codex her turda "service tier ... will be
|
||||
# omitted" uyarisi basar); istekteki service_tier alani gecide gonderilmeden once silinir.
|
||||
if dis and CFG.get("dis_arac_modu", "direct") == "direct":
|
||||
# Abonelik kalibi "code_mode_only": tek arac JavaScript calistiran `exec`. Zayif modeller bunu
|
||||
# cagiramiyor ya da ciktiyi yazdirmiyor (10 Ekim 2026 olcumu). Dis modellere Codex'in klasik
|
||||
# araclari dogrudan verilir (GPT-5.5 kalibindaki gibi); Responses Lite kapatilir.
|
||||
m["tool_mode"] = None
|
||||
m["use_responses_lite"] = False
|
||||
m["experimental_supported_tools"] = []
|
||||
if x.get("context_window"):
|
||||
cw = int(x["context_window"])
|
||||
m["context_window"] = cw; m["max_context_window"] = cw
|
||||
@@ -803,6 +990,34 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
return self._send_json(400, {"error": {"message": f"bridge: govde cozulemedi: {e}"}})
|
||||
slug = req.get("model", "")
|
||||
mapping = MODEL_MAP.get(slug)
|
||||
if CFG.get("nokta_karsila", True) and nokta_mesaji_mi(req):
|
||||
# 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.
|
||||
ad = next((m.get("display_name") for m in EXTRA_MODELS if m.get("slug") == slug), None) or slug
|
||||
govde = hazir_yaniti(slug, f"{ad} hazır.")
|
||||
log(f"[{n} {slug}] nokta mesaji kopruden karsilandi (modele gonderilmedi)")
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.send_header("Content-Length", str(len(govde)))
|
||||
self.end_headers()
|
||||
self.wfile.write(govde)
|
||||
return
|
||||
# Ayni sohbetin onceki cevabi yarida kesildiyse (yarim.py) metni bu istege not olarak eklenir.
|
||||
sohbet = str(req.get("prompt_cache_key") or "")
|
||||
yarim_eklenen = 0
|
||||
if CFG.get("yarim_cevap", True):
|
||||
try:
|
||||
yarim_eklenen = yarim.uygula(req, sohbet)
|
||||
except Exception as e:
|
||||
log(f"[{n} {slug}] yarim cevap notu atlandi: {type(e).__name__}: {e}")
|
||||
if yarim_eklenen:
|
||||
log(f"[{n} {slug}] yarim cevap notu eklendi ({yarim_eklenen} karakter)")
|
||||
izle = yarim.Izleyici(sohbet, slug) if (sohbet and CFG.get("yarim_cevap", True)) else None
|
||||
try:
|
||||
if not ozet_kopru.tetik_mi(req):
|
||||
OZET_FRENI.normal(sohbet or slug) # ozet olmayan istek: art arda ozet sayaci sifirlanir
|
||||
except Exception:
|
||||
pass
|
||||
ozet_cfg = CFG.get("ozet_kopru") or {}
|
||||
ozet_acik = ozet_cfg.get("enabled", False)
|
||||
catalog = CFG.get("nine_router_catalog") or {}
|
||||
@@ -822,7 +1037,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
# abonelik modeli: chatgpt.com'a oldugu gibi
|
||||
fixed = fix_tool_ids(req)
|
||||
atilan = drop_foreign_items(req)
|
||||
degisti = False
|
||||
degisti = bool(yarim_eklenen)
|
||||
ozet_akisi = None
|
||||
if ozet_acik:
|
||||
# Ozet bagdastirici (ozet_kopru.py; 9 Ekim 2026'da yalniz bu parca geri acildi): koprunun urettigi
|
||||
@@ -832,7 +1047,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
# kapsam "hepsi": OpenAI modelinde de ozeti kopru DUZ METIN urettirir; boylece ozet sonradan
|
||||
# secilen her saglayicida okunur. kapsam "9router": OpenAI ozeti yerlesik (sifreli) kalir.
|
||||
if ozet_cfg.get("kapsam", "hepsi") == "hepsi" and ozet_kopru.tetik_mi(req):
|
||||
OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 3)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600))
|
||||
OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 8)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600)); OZET_FRENI.art_arda = int(ozet_cfg.get("dongu_art_arda", 3))
|
||||
if not OZET_FRENI.izin(str(req.get("prompt_cache_key") or slug), time.time()):
|
||||
log(f"[{n} CHATGPT {slug}] ozet kopru: DONGU FRENI, ozet istegi reddedildi")
|
||||
return self._send_json(400, {"error": {"message": ozet_kopru.DONGU_MESAJI % OZET_FRENI.sinir,
|
||||
@@ -860,7 +1075,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"[{n} CHATGPT {slug}] ozet kopru: {ozet_akisi.sonuc_notu()}")
|
||||
return sonuc
|
||||
return self._passthrough(n, path, body, tagname=f"CHATGPT {slug}",
|
||||
rewritten=bool(fixed or atilan or degisti))
|
||||
rewritten=bool(fixed or atilan or degisti), izle=izle)
|
||||
route_name, target, flat = mapping
|
||||
try:
|
||||
ozet_tetik = ozet_acik and ozet_kopru.tetik_mi(req)
|
||||
@@ -869,7 +1084,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"[{n} {slug}] ozet kopru atlandi: {type(e).__name__}: {e}")
|
||||
girdi_bayt = 0
|
||||
if ozet_tetik:
|
||||
OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 3)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600))
|
||||
OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 8)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600)); OZET_FRENI.art_arda = int(ozet_cfg.get("dongu_art_arda", 3))
|
||||
if not OZET_FRENI.izin(str(req.get("prompt_cache_key") or slug), time.time()):
|
||||
log(f"[{n} {slug}] ozet kopru: DONGU FRENI, ozet istegi reddedildi (ayni sohbette {OZET_FRENI.sinir} ozetten sonra)")
|
||||
return self._send_json(400, {"error": {"message": ozet_kopru.DONGU_MESAJI % OZET_FRENI.sinir,
|
||||
@@ -892,6 +1107,8 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
tag = f"[{n} {route_name.upper()} {slug}->{target}]"
|
||||
req["model"] = target
|
||||
req.pop("client_metadata", None)
|
||||
if route_name == ide_route:
|
||||
req.pop("service_tier", None) # abonelik hiz katmani; gecit tanimaz
|
||||
for k in route.get("strip_fields", []):
|
||||
req.pop(k, None)
|
||||
if ozet_acik:
|
||||
@@ -904,11 +1121,11 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
rewriter = None
|
||||
if flat if flat is not None else route.get("flatten_tools"):
|
||||
custom = flatten_request(req)
|
||||
rewriter = SseRewriter(custom)
|
||||
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)
|
||||
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-'):
|
||||
@@ -926,6 +1143,18 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
eklendi, sonlar = claude_sonu_duzelt(req)
|
||||
if eklendi:
|
||||
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")
|
||||
if hedef_pencere and pencere_biliniyor and CFG.get("tasma_bekcisi", True) and not ozet_tetik:
|
||||
kirpilan, sigdi, tahmin = pencere.sigdir(req, hedef_pencere)
|
||||
if kirpilan:
|
||||
log(f"{tag} tasma bekcisi: {kirpilan} buyuk eski parca kisaltildi (tahmini {tahmin} token, pencere {hedef_pencere})")
|
||||
if not sigdi:
|
||||
log(f"{tag} tasma bekcisi: istek gonderilmedi (tahmini {tahmin} token > pencere {hedef_pencere})")
|
||||
return self._send_sse_failed("context_length_exceeded",
|
||||
f"Bu mesaj seçili modelin bağlam penceresine sığmıyor (yaklaşık {tahmin} token, pencere {hedef_pencere}). "
|
||||
"Daha büyük pencereli bir model seçin ya da mesajı kısaltın.")
|
||||
data = json.dumps(req, ensure_ascii=False).encode("utf-8")
|
||||
log(f"{tag} girdi={len(req.get('input', []))} oge, arac={len(req.get('tools', []) or [])} {len(data)} bayt")
|
||||
if DEBUG:
|
||||
@@ -956,6 +1185,11 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
log(f"{tag} deneme {attempt}: HTTP {e.code} {errbody[:100]!r} -> tekrar")
|
||||
time.sleep(min(2 * attempt, 8)); continue
|
||||
log(f"{tag} HTTP {e.code} {errbody[:200]!r}")
|
||||
if pencere.tasma_mi(etxt):
|
||||
# Hedef "baglam sigmiyor" dedi: Codex'in tanidigi tasma hatasi olarak iletilir ve hedefin
|
||||
# bildirdigi gercek sinir ogrenilir (sonraki model listesinde pencere kuculur).
|
||||
self._tasma_ogren(target, etxt)
|
||||
return self._send_sse_failed("context_length_exceeded", etxt[:300])
|
||||
self.send_response(e.code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(errbody)))
|
||||
@@ -973,7 +1207,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
sonuc = self._stream(resp, resp.status, tag, akis)
|
||||
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
|
||||
return sonuc
|
||||
return self._stream(resp, resp.status, tag, rewriter)
|
||||
return self._stream(resp, resp.status, tag, rewriter, izle)
|
||||
self._send_json(503, {"error": {"message": f"bridge: hedef ulasilamadi {last}"}})
|
||||
|
||||
do_POST = _handle
|
||||
|
||||
Reference in new issue
Block a user