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
@@ -0,0 +1,292 @@
|
||||
"""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
|
||||
Reference in new issue
Block a user