"""Eklenen modellerin gercek baglam penceresi ve yetenekleri (10 Ekim 2026). Neden: gecit model basina pencere vermiyor; kopru her modele abonelik kalibinin penceresini (272000) kopyaliyordu. Codex, kucuk pencereli modele geciste sohbeti kendisi sikistirir, ama bunu ilan edilen pencereye gore yapar (core/src/session/turn.rs maybe_run_previous_model_inline_compact). Pencere dogru ilan edilince o yol kendiliginden calisir; yanlis ilan edilince istek hedefe sigmaz. Kaynak sirasi (kucukten guvenilire degil, guvenilirden yedege): 1. Hedefin kendi hatasindan ogrenilen sinir (ogren / ogrenilen): en guvenilir, diske yazilir. 2. NanoGPT'nin acik model listesi (nano/... modelleri icin istegi tasiyan saglayicinin kendi degeri). 3. Saglayici kisaltmasina gore yerel tablo (cc, cx, ag). 4. Bilinmeyen: VARSAYILAN (temkinli) ve bir kez gunluk satiri. Combo: yapraklarinin en kucugu (yedege dusulurse de sigsin). Ust sinir TAVAN: ozeti abonelik modeli yazdigi icin gecmis onun penceresini asmamali. """ import json import os import re import time import urllib.request TAVAN = 272_000 # abonelik modellerinin penceresi; ozetleyen model bundan fazlasini okuyamaz VARSAYILAN = 128_000 # hakkinda hicbir sey bilinmeyen model TABAN = 8_000 # saglayici verisi bozuksa (0, eksi) kullanilmaz NANO_URL = "https://nano-gpt.com/api/v1/models?detailed=true" NANO_OMUR_SN = 24 * 3600 NANO_DOSYA = "pencere-nano.json" OGRENILEN_DOSYA = "pencere-ogrenilen.json" ARACSIZ_DOSYA = "aracsiz-modeller.json" # Saglayici kisaltmasi -> (pencere, gorsel girdi, arac cagrisi). cc = Claude Code aboneligi, cx = Codex # aboneligi, ag = Antigravity. Degerler abonelik yollarinin temkinli sinirlaridir. _YEREL = ( (re.compile(r"^cx/"), (272_000, True, True)), (re.compile(r"^cc/"), (200_000, True, True)), (re.compile(r"^ag/claude"), (200_000, True, True)), (re.compile(r"^ag/gemini"), (1_000_000, True, True)), (re.compile(r"^ag/gpt-oss"), (131_072, False, True)), ) # "maximum context length is 131072 tokens", "prompt is too long: 3040251 tokens > 1000000 maximum", # "context window of 32768 tokens", "exceeds the limit of 65536" ... _SINIR = ( re.compile(r"maximum context length (?:is|of) (\d{4,8})", re.I), re.compile(r"\d{4,9} tokens? > (\d{4,8}) maximum", re.I), re.compile(r"context (?:window|length|size)(?: limit)? (?:is|of) (\d{4,8})", re.I), re.compile(r"(?:limit|maximum) of (\d{4,8}) tokens", re.I), re.compile(r"max(?:imum)? context tokens\D{0,6}(\d{4,8})", re.I), ) _TASMA = re.compile(r"context[_ ](?:length|window)[_ ]exceeded|maximum context length|prompt is too long|" r"context window|too many tokens|input is too long|exceeds? the (?:maximum |model.s )?context|max(?:imum)? context tokens", re.I) def _oku(yol): try: with open(yol, encoding="utf-8") as f: v = json.load(f) return v if isinstance(v, dict) else {} except (OSError, ValueError): return {} def _yaz(yol, veri): gecici = yol + ".tmp" with open(gecici, "w", encoding="utf-8") as f: json.dump(veri, f, ensure_ascii=False) os.replace(gecici, yol) def nano_sadelestir(ham): """NanoGPT yaniti -> {model kimligi (kucuk harf): {"pencere", "gorsel", "arac"}}.""" cikti = {} for m in (ham or {}).get("data") or []: kimlik = str(m.get("id") or "").lower() pencere = m.get("context_length") if not kimlik or not isinstance(pencere, int) or pencere < TABAN: continue yetenek = m.get("capabilities") or {} cikti[kimlik] = {"pencere": pencere, "gorsel": bool(yetenek.get("vision")), "arac": bool(yetenek.get("tool_calling"))} return cikti def nano_yukle(home, ua="AstraBridge/1.0 (codex)", timeout=20, simdi=None, getir=None, ag=True): """Onbellek tazeyse onu, degilse agdan yenisini dondurur. Ag hatasinda eski onbellek (yasina bakilmadan) kullanilir; o da yoksa bos sozluk doner ve cagiran yerel tabloya duser. Hicbir durumda istisna atmaz. ag=False: aga cikilmaz, eldeki onbellek doner (koprunun acilisi beklemesin diye).""" simdi = time.time() if simdi is None else simdi yol = os.path.join(home, NANO_DOSYA) if home else "" onbellek = _oku(yol) if yol else {} if onbellek.get("modeller") and simdi - float(onbellek.get("zaman") or 0) < NANO_OMUR_SN: return onbellek["modeller"] if not ag: return onbellek.get("modeller") or {} try: if getir is None: istek = urllib.request.Request(NANO_URL, headers={"User-Agent": ua, "Accept": "application/json"}) with urllib.request.urlopen(istek, timeout=timeout) as resp: ham = json.load(resp) else: ham = getir() modeller = nano_sadelestir(ham) if modeller: if yol: try: _yaz(yol, {"zaman": simdi, "modeller": modeller}) except OSError: pass return modeller except Exception: # noqa: BLE001 - ag, JSON, sertifika: hepsi "yerel tabloya dus" demek pass return onbellek.get("modeller") or {} def ogrenilen(home): return {k: int(v) for k, v in _oku(os.path.join(home, OGRENILEN_DOSYA)).items() if isinstance(v, (int, float)) and v >= TABAN} if home else {} def aracsizlar(home): """Hedefin "arac cagrisi desteklenmiyor" dedigi modeller (kimlik kumesi).""" return {k for k, v in _oku(os.path.join(home, ARACSIZ_DOSYA)).items() if v} if home else set() def aracsiz_mi(metin): return bool(re.search(r"does not support tool|tool_choice_unsupported|tools? (?:are|is) not supported|" r"function calling is not supported", metin or "", re.I)) def ogren_aracsiz(home, kimlik): """True: kayit yeni eklendi.""" if not home or not kimlik: return False yol = os.path.join(home, ARACSIZ_DOSYA) veri = _oku(yol) if veri.get(kimlik): return False veri[kimlik] = True try: _yaz(yol, veri) except OSError: return False return True def tasma_mi(metin): return bool(_TASMA.search(metin or "")) def sinir_oku(metin): """Hedefin tasma hatasindaki gercek sinir; yoksa None.""" for kalip in _SINIR: m = kalip.search(metin or "") if m and int(m.group(1)) >= TABAN: return int(m.group(1)) return None def ogren(home, kimlik, sinir): """Hedefin bildirdigi siniri kaydeder (yalniz kucultur). True: kayit degisti.""" if not home or not kimlik or not sinir or sinir < TABAN: return False yol = os.path.join(home, OGRENILEN_DOSYA) veri = _oku(yol) if isinstance(veri.get(kimlik), (int, float)) and veri[kimlik] <= sinir: return False veri[kimlik] = int(sinir) try: _yaz(yol, veri) except OSError: return False return True def yol_bilgisi(yol, nano): """"nano/z-ai/glm-5.3" ya da "cc/claude-opus-5-5" -> {"pencere","gorsel","arac","kaynak"} ya da None.""" yol = (yol or "").strip() if yol.startswith("9router/"): yol = yol[len("9router/"):] if yol.startswith("nano/"): n = (nano or {}).get(yol[len("nano/"):].lower()) if n: return {**n, "kaynak": "nanogpt"} return None for kalip, (pencere, gorsel, arac) in _YEREL: if kalip.match(yol): return {"pencere": pencere, "gorsel": gorsel, "arac": arac, "kaynak": "yerel"} return None def _yapraklar(dugum, derinlik=0): if not isinstance(dugum, dict) or derinlik > 6: return [] if dugum.get("kind") != "combo": return [dugum.get("model")] if dugum.get("model") else [] cikti = [] for uye in dugum.get("members") or []: cikti.extend(_yapraklar(uye, derinlik + 1)) return cikti def coz(kimlik, tur, yol, kayit=None, nano=None, ogr=None, aracsiz=()): """Bir katalog girdisinin ilan edilecek penceresi ve yetenekleri. kimlik: gecit kimligi; tur: "combo" | "model"; yol: gecidin aciklamasindaki "saglayici/model" yolu; kayit: /router/catalog girdisi (combo topolojisi icin). Donus: {"pencere","gorsel","arac","kaynak"}.""" bilinmeyen = {"pencere": VARSAYILAN, "gorsel": False, "arac": True, "kaynak": "varsayilan"} if tur == "combo": yapraklar = _yapraklar((kayit or {}).get("topology")) bilgiler = [yol_bilgisi(y, nano) or dict(bilinmeyen) for y in yapraklar] if not bilgiler: sonuc = dict(bilinmeyen) else: sonuc = {"pencere": min(b["pencere"] for b in bilgiler), # Bir takim uyesi resmi goremese de digerleri gorur; sirali yedekte ilk uye belirler. "gorsel": bool(bilgiler[0]["gorsel"]), "arac": bool(bilgiler[0]["arac"]), "kaynak": "uyeler:" + ",".join(sorted({b["kaynak"] for b in bilgiler}))} else: if (yol or "").startswith("no-think/"): # Dusunmesiz kopya: asil saglayici kimlikten okunur ("no-think-9router-cc-claude-..." -> cc). m = re.match(r"no-think-(?:9router-)?([a-z0-9]+)-", kimlik or "") yol = (m.group(1) + "/" + yol[len("no-think/"):]) if m else yol sonuc = yol_bilgisi(yol, nano) or dict(bilinmeyen) if kimlik in aracsiz: sonuc = {**sonuc, "arac": False} sinir = (ogr or {}).get(kimlik) if sinir and sinir < sonuc["pencere"]: sonuc = {**sonuc, "pencere": int(sinir), "kaynak": "ogrenilen"} sonuc["pencere"] = max(TABAN, min(TAVAN, int(sonuc["pencere"]))) return sonuc def kisa(pencere): """8192 -> "8K", 131072 -> "128K", 272000 -> "272K".""" return f"{round(pencere / 1024) if pencere < 262_144 and pencere % 1024 == 0 else round(pencere / 1000)}K" # ---- Istek oncesi tasma bekcisi ---- # Codex sikistirmadan sonra kullanici mesajlarini AYNEN tutar; cok buyuk bir yapistirma (ya da dev arac # ciktisi) kucuk pencereli hedefe yine sigmaz ve hedef bos akisla doner (10 Ekim 2026 olcumu: 390 KB istek, # 64K pencere, Codex 5 kez yeniden deneyip "stream closed" ile dustu). Bekci, hedefe giden KOPYADA en buyuk # eski metin parcalarinin ortasini cikarir; yine sigmazsa istegi gondermeden anlasilir hata doner. BAYT_BOLU_TOKEN = 3.2 # olcum (10 Ekim 2026): 540 KB duz Turkce metin = 171016 token (3,16 bayt/token) DOLULUK = 0.97 # pencerenin bu oranini asan istek kirpilir / gonderilmez KIRP_ESIK = 6000 # bundan kisa metin parcasi kirpilmaz KIRP_BAS = KIRP_SON = 1500 def tahmini_token(req): return int(len(json.dumps(req, ensure_ascii=False).encode("utf-8")) / BAYT_BOLU_TOKEN) def _metin_parcalari(req): """Kirpilabilir (sahip, anahtar) ciftleri: arac ciktilari ile eski kullanici/asistan mesajlari. Son kullanici mesajina ve talimat tasiyan developer/system mesajlarina (Codex kurallari, skill yonergeleri, kanca baglami) dokunulmaz.""" girdi = req.get("input") if not isinstance(girdi, list): return [] son_kullanici = max((i for i, it in enumerate(girdi) if isinstance(it, dict) and it.get("role") == "user" and it.get("type", "message") == "message"), default=-1) parcalar = [] for i, it in enumerate(girdi): if not isinstance(it, dict) or i == son_kullanici or it.get("role") in ("developer", "system"): continue for alan in ("content", "output"): deger = it.get(alan) if isinstance(deger, str): parcalar.append((it, alan)) elif isinstance(deger, list): for p in deger: if isinstance(p, dict) and isinstance(p.get("text"), str): parcalar.append((p, "text")) return parcalar def sigdir(req, pencere_token): """Istegi hedef pencereye sigdirir. Donus: (kirpilan parca sayisi, sigdi mi, tahmini token).""" sinir = int(pencere_token * DOLULUK) tahmin = tahmini_token(req) if not pencere_token or tahmin <= sinir: return 0, True, tahmin kirpilan = 0 parcalar = sorted(_metin_parcalari(req), key=lambda sa: len(sa[0][sa[1]]), reverse=True) for sahip, anahtar in parcalar: metin = sahip[anahtar] if len(metin) < KIRP_ESIK: break sahip[anahtar] = (metin[:KIRP_BAS] + f"\n[... {len(metin) - KIRP_BAS - KIRP_SON} karakter çıkarıldı (köprü): " f"seçili modelin bağlam penceresine sığmıyordu ...]\n" + metin[-KIRP_SON:]) kirpilan += 1 tahmin = tahmini_token(req) if tahmin <= sinir: return kirpilan, True, tahmin return kirpilan, tahmin <= sinir, tahmin