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>
353 lines
17 KiB
Python
353 lines
17 KiB
Python
"""AITURK IDE "Codex Baglama": IDE'nin model ve combolarini Codex listesine ekler.
|
||
|
||
Kaynak, IDE'nin kullandigi gecittir (config.yaml -> providers.turkservis.base_url);
|
||
anahtar IDE'nin kendi .env dosyasindan her yenilemede okunur, bridge.json'a yazilmaz.
|
||
|
||
Ac/kapat: %LOCALAPPDATA%\\TurkServis\\AITURK-IDE\\codex-baglama.json
|
||
{"enabled": true, "kapsam": "ide"}
|
||
Dosya yoksa ACIK sayilir (kullanici karari, 9 Ekim 2026: varsayilan acik).
|
||
kapsam "ide" ve "hepsi": gecidin uye katalogundaki OmniRoute sohbet modelleri.
|
||
Yerel config'teki model adlari uye katalogunu sinirlamaz.
|
||
"""
|
||
import json, os, re, urllib.request
|
||
|
||
import pencere
|
||
|
||
# Agent home: HERMES_HOME when the IDE passes it, else the default install location.
|
||
AGENT = os.environ.get("HERMES_HOME") or os.path.join(os.environ.get("LOCALAPPDATA", ""), "TurkServis", "AITURK-IDE", "agent")
|
||
IDE_DIR = os.path.dirname(os.path.normpath(AGENT))
|
||
AYAR = os.path.join(IDE_DIR, "codex-baglama.json")
|
||
KEY_NAME = "HERMES_CUSTOM_TURKSERVIS_API_KEY"
|
||
DEFAULT_BASE = "https://ai.turkservis.online/v1"
|
||
|
||
|
||
def ayar_oku(yol=AYAR):
|
||
"""Dosya yoksa ya da bozuksa varsayilan: acik, kapsam 'ide'."""
|
||
ayar = {"enabled": True, "kapsam": "ide"}
|
||
try:
|
||
with open(yol, encoding="utf-8") as f:
|
||
v = json.load(f)
|
||
if isinstance(v, dict):
|
||
if isinstance(v.get("enabled"), bool):
|
||
ayar["enabled"] = v["enabled"]
|
||
if v.get("kapsam") in ("ide", "hepsi"):
|
||
ayar["kapsam"] = v["kapsam"]
|
||
except (OSError, ValueError):
|
||
pass
|
||
return ayar
|
||
|
||
|
||
def anahtar_oku(agent=AGENT):
|
||
try:
|
||
with open(os.path.join(agent, ".env"), encoding="utf-8", errors="replace") as f:
|
||
for line in f:
|
||
if line.startswith(KEY_NAME + "="):
|
||
return line.split("=", 1)[1].strip().strip('"').strip("'")
|
||
except OSError:
|
||
pass
|
||
return ""
|
||
|
||
|
||
def ide_yapilandirma(agent=AGENT):
|
||
"""config.yaml'dan gecit adresi, varsayilan model ve tanimli model adlari.
|
||
Kopru bagimliliksiz calistigi icin YAML'in yalniz bu bolumu satir satir okunur."""
|
||
base, varsayilan, adlar = DEFAULT_BASE, "", []
|
||
try:
|
||
with open(os.path.join(agent, "config.yaml"), encoding="utf-8", errors="replace") as f:
|
||
lines = f.read().splitlines()
|
||
except OSError:
|
||
return base, varsayilan, adlar
|
||
bolum = saglayici = None
|
||
modellerde = False
|
||
for line in lines:
|
||
if not line.strip() or line.lstrip().startswith("#"):
|
||
continue
|
||
girinti = len(line) - len(line.lstrip(" "))
|
||
metin = line.strip()
|
||
if girinti == 0:
|
||
bolum = metin.rstrip(":"); saglayici = None; modellerde = False
|
||
continue
|
||
if bolum == "model" and girinti == 2:
|
||
if metin.startswith("default:"):
|
||
varsayilan = metin.split(":", 1)[1].strip().strip("'\"")
|
||
continue
|
||
if bolum != "providers":
|
||
continue
|
||
if girinti == 2:
|
||
saglayici = metin.rstrip(":"); modellerde = False
|
||
elif saglayici == "turkservis" and girinti == 4:
|
||
modellerde = metin == "models:"
|
||
if metin.startswith("base_url:"):
|
||
base = metin.split(":", 1)[1].strip().strip("'\"") or base
|
||
elif saglayici == "turkservis" and modellerde and girinti == 6:
|
||
ad = re.split(r":\s*(\{|$)", metin)[0].strip().strip("'\"")
|
||
if ad:
|
||
adlar.append(ad)
|
||
return base.rstrip("/"), varsayilan, adlar
|
||
|
||
|
||
AITURK_EKI = " (AITURK)"
|
||
# Gecidin kisa saglayici kodu -> kullanicinin gormek istedigi kod (10 Ekim 2026: "agy/gemini").
|
||
_SAGLAYICI_KISA = {"ag": "agy"}
|
||
# Saglayici kodu zaten soyledigi icin model adinin basinda tekrar edilmeyen uretici adi ("cc/opus-5-5").
|
||
_TEKRAR = ("claude-",)
|
||
DUSUNMESIZ = "no-think-"
|
||
|
||
|
||
def kisa_ad(ad):
|
||
"""Gecit onekleri ("OmniRoute · ", "9Router · ", "9R · ") ve aradaki saglayici halkalari atilir:
|
||
"nano/qwen/qwen3.8-27b" -> "nano/qwen3.8-27b", "9R · ag/gemini-3.8-flash" -> "ag/gemini-3.8-flash",
|
||
combo adi oldugu gibi kalir. Kullanici karari (10 Ekim 2026): "9R" yazilmaz."""
|
||
ad = (ad or "").split(" · ")[-1].strip()
|
||
parcalar = [p for p in ad.split("/") if p]
|
||
if len(parcalar) <= 2:
|
||
return "/".join(parcalar) or ad
|
||
return parcalar[0] + "/" + parcalar[-1]
|
||
|
||
|
||
def gorunen_ad(kimlik, ad, tur="model"):
|
||
"""Secicide gorunen ad (kullanici karari, 10 Ekim 2026): combo yalniz kendi adiyla; tek model
|
||
"saglayici/model" kalibinda, kisa ve surumlu: "9R · cc/claude-opus-5-5" -> "cc/opus-5-5",
|
||
"9R · ag/gemini-3.8-flash-high" -> "agy/gemini-3.8-flash-high", "Agnes 2.0 Flash" (agnes-agnes-2.0-flash)
|
||
-> "agnes/agnes-2.0-flash", "kurnaz" (bekci-kurnaz) -> "bekci/kurnaz". Dusunmesiz kopya asil
|
||
saglayicisiyla gorunur: "no-think/claude-opus-5-5" -> "cc/opus-5-5 (no-think)"."""
|
||
yalin = kisa_ad(ad)
|
||
if tur == "combo":
|
||
return yalin
|
||
kim, ek = kimlik or "", ""
|
||
if kim.startswith(DUSUNMESIZ):
|
||
kim, ek = kim[len(DUSUNMESIZ):], " (no-think)"
|
||
if kim.startswith("9router-"):
|
||
kim = kim[len("9router-"):]
|
||
saglayici, _, model = yalin.partition("/")
|
||
if not model or ek:
|
||
kod, _, kalan = kim.partition("-")
|
||
if not kod or not kalan:
|
||
return yalin
|
||
saglayici, model = kod, model or kalan
|
||
model = model.replace(" ", "-")
|
||
for tekrar in _TEKRAR:
|
||
if model.lower().startswith(tekrar) and len(model) > len(tekrar):
|
||
model = model[len(tekrar):]
|
||
return f"{_SAGLAYICI_KISA.get(saglayici.lower(), saglayici)}/{model}{ek}"
|
||
|
||
|
||
def adlandir(girdiler, dolu=(), turler=None):
|
||
"""[(kimlik, tam_ad)] -> {kimlik: gorunen_ad}. Kisa adlar cakisirsa aradaki halkalar geri konur;
|
||
yine cakisirsa kimligin sonu eklenir. `dolu`: baska kaynaktan gelen, zaten kullanilan adlar.
|
||
`turler` ({kimlik: "combo"|"model"}) verilirse adlar gorunen_ad kuraliyla yazilir."""
|
||
if turler is None:
|
||
kisa = {k: kisa_ad(a) for k, a in girdiler}
|
||
else:
|
||
kisa = {k: gorunen_ad(k, a, turler.get(k, "model")) for k, a in girdiler}
|
||
tam = {k: (a or "").split(" · ")[-1].strip() for k, a in girdiler}
|
||
sayac = {}
|
||
for v in list(kisa.values()) + list(dolu):
|
||
sayac[v.lower()] = sayac.get(v.lower(), 0) + 1
|
||
sonuc, kullanilan = {}, {d.lower() for d in dolu}
|
||
for k, _ in girdiler:
|
||
ad = kisa[k] if sayac[kisa[k].lower()] == 1 else tam[k]
|
||
if ad.lower() in kullanilan:
|
||
ad = f"{ad} [{k[-8:]}]"
|
||
kullanilan.add(ad.lower())
|
||
sonuc[k] = ad
|
||
return sonuc
|
||
|
||
|
||
def sec(data, tanimli=(), kapsam="ide", turler=None):
|
||
"""Gecidin uye katalogundan girdiler: [{'id','ad','kind'}], sirali ve tekrarsiz.
|
||
Yalniz sohbet modelleri; resim/ses/video/embedding uretenler eklenmez."""
|
||
turler = turler or {}
|
||
secilen, gorulen = [], set()
|
||
for m in data:
|
||
mid = m.get("id") or ""
|
||
ts = m.get("turkservis") or {}
|
||
if not mid or mid in gorulen or "chat" not in (ts.get("capabilities") or ["chat"]):
|
||
continue
|
||
gorunen = ts.get("display_name") or mid
|
||
kind = turler.get(mid) or ts.get("kind") or "model"
|
||
# Tek uye yolu: 9Router -> OmniRoute -> TurkServis -> Codex.
|
||
# Dogrudan 9Router satirlari ayni modelin ikinci kopyasi olarak eklenmez.
|
||
if ts.get("source") != "omniroute":
|
||
continue
|
||
# Combo adi biciminden tahmin edilmez; duz adli Agnes/Bekci modelleri de vardir.
|
||
if kind == "combo" and mid.lower().startswith("9router-"):
|
||
continue
|
||
gorulen.add(mid)
|
||
secilen.append({"id": mid, "ad": gorunen, "kind": kind, "sira": ts.get("order") or 0,
|
||
"yol": str(m.get("description") or "").split(" · ")[-1].strip()})
|
||
secilen.sort(key=lambda s: (s["sira"], s["id"]))
|
||
# Gecit ayni adin ikinci yoluna "ad (2)" der; birincisi listede yoksa sayac anlamsizdir.
|
||
adlar = {s["ad"].split(" · ")[-1].strip().lower() for s in secilen}
|
||
for s in secilen:
|
||
taban = re.sub(r" \(\d+\)$", "", s["ad"])
|
||
if taban != s["ad"] and taban.split(" · ")[-1].strip().lower() not in adlar:
|
||
adlar.add(taban.split(" · ")[-1].strip().lower())
|
||
s["ad"] = taban
|
||
return [{"id": s["id"], "ad": s["ad"], "kind": s["kind"], "yol": s["yol"]} for s in secilen]
|
||
|
||
|
||
def sirala_codex(modeller, ekler):
|
||
"""Ozgun abonelik modelleri oldugu gibi kalir: sirasi, priority degeri ve adi degismez,
|
||
listenin basinda durur. Ek modeller onlarin ARKASINA eklenir: combo A-Z, sonra
|
||
saglayici A-Z / model A-Z. Ozgun bir modelle ayni adi tasiyan ek model " (AITURK)"
|
||
ekiyle ayirt edilir; secicide ozgunun yerine gecmis gibi gorunmez.
|
||
Kimlik ve yetenek alanlari korunur; kayitli model secimine dokunulmaz."""
|
||
bilgiler = {m["slug"]: m for m in ekler}
|
||
ozgunler = [m for m in modeller if m.get("slug", "") not in bilgiler]
|
||
ek_modeller = [m for m in modeller if m.get("slug", "") in bilgiler]
|
||
def yalin(ad): # "GPT-6-1-Sol" ile "GPT-6.1-Sol" ayni ad sayilir
|
||
return re.sub(r"[^0-9a-z]+", "", (ad or "").casefold())
|
||
|
||
ozgun_adlar = {yalin(m.get("display_name") or m.get("slug", "")) for m in ozgunler}
|
||
|
||
def anahtar(m):
|
||
slug = m.get("slug", "")
|
||
bilgi = bilgiler.get(slug, {})
|
||
ad = kisa_ad(m.get("display_name") or slug)
|
||
if bilgi.get("kind") == "combo":
|
||
return (0, ad.casefold(), "", slug)
|
||
if slug.startswith("ide/"):
|
||
mid = slug[len("ide/"):]
|
||
if mid.startswith("9router-"):
|
||
mid = mid[len("9router-"):]
|
||
saglayici, _, model = ad.partition("/")
|
||
if not model:
|
||
saglayici, _, _ = mid.partition("-")
|
||
model = ad
|
||
else:
|
||
saglayici, model = "OpenAI", ad
|
||
return (1, saglayici.casefold(), model.casefold(), slug)
|
||
|
||
ek_modeller.sort(key=anahtar)
|
||
taban = max([m.get("priority") or 0 for m in ozgunler], default=-1) + 1
|
||
for n, model in enumerate(ek_modeller):
|
||
model["priority"] = taban + n
|
||
ad = model.get("display_name") or model.get("slug", "")
|
||
if yalin(ad) in ozgun_adlar:
|
||
model["display_name"] = f"{ad}{AITURK_EKI}"
|
||
modeller[:] = ozgunler + ek_modeller
|
||
|
||
|
||
# ---- Secicide model adinin altindaki satir ----
|
||
# Telefonda bu satir tek satirdir ve yaklasik 30 karakterde kesilir (kullanici ekran goruntusu,
|
||
# 10 Ekim 2026); en ayirt edici bilgi basa yazilir, ad tekrar edilmez.
|
||
ACIKLAMA_SINIRI = 30
|
||
KUCUK_PENCERE = 100_000 # bunun altindaki pencere alt satirda yazilir
|
||
_AILE = (("opus", "opus"), ("sonnet", "sonnet"), ("haiku", "haiku"), ("astra", "astra"), ("terra", "terra"),
|
||
("luna", "luna"), ("sol", "sol"), ("gemini", "gemini"), ("qwen", "qwen"), ("glm", "glm"),
|
||
("abliterated", "abliterated"), ("deepseek", "deepseek"), ("llama", "llama"), ("gemma", "gemma"))
|
||
_SAGLAYICI = {"cc": "Claude", "cx": "Codex", "ag": "Antigravity", "agy": "Antigravity", "nano": "NanoGPT", "hf": "HuggingFace",
|
||
"huggingface": "HuggingFace", "agnes": "Agnes", "bekci": "Bekçi"}
|
||
_SANSURSUZ = re.compile(r"uncensored|abliterat|obliterat|derestrict|heretic|unshackled", re.I)
|
||
|
||
|
||
def kisa_model(ad):
|
||
""""cc/claude-opus-5-5" -> "opus", "nano/z-ai/glm-5.3-flash-uncensored" -> "glm"; bilinmeyen ad "?"."""
|
||
if not ad:
|
||
return "?"
|
||
son = ad.split("/")[-1].split(":")[0].lower()
|
||
parcalar = [p for p in re.split(r"[^a-z]+", son) if p]
|
||
for anahtar, kisa in _AILE:
|
||
if anahtar in parcalar:
|
||
return kisa
|
||
return (parcalar[0] if parcalar else son) or "?"
|
||
|
||
|
||
def _yapraklar(dugum, derinlik=0):
|
||
"""Sirali (priority) zinciri duz model listesine acar; ic takim kendi uyeleriyle yer alir."""
|
||
if not isinstance(dugum, dict) or derinlik > 6:
|
||
return ["?"]
|
||
if dugum.get("kind") != "combo":
|
||
return [kisa_model(dugum.get("model"))]
|
||
cikti = []
|
||
for uye in dugum.get("members") or []:
|
||
cikti.extend(_yapraklar(uye, derinlik + 1))
|
||
return cikti or ["?"]
|
||
|
||
|
||
def _sigdir(onek, adlar, ayrac):
|
||
"""Sinira sigdigi kadar ad yazar, kalani "+N" ile sayar."""
|
||
tekil = [a for i, a in enumerate(adlar) if i == 0 or a != adlar[i - 1]]
|
||
for adet in range(len(tekil), 0, -1):
|
||
kalan = len(tekil) - adet
|
||
metin = onek + ayrac.join(tekil[:adet]) + (f" +{kalan}" if kalan else "")
|
||
if len(metin) <= ACIKLAMA_SINIRI or adet == 1:
|
||
return metin
|
||
return onek.rstrip(": ")
|
||
|
||
|
||
def aciklama(kimlik, ad, tur, kayit=None, bilgi=None):
|
||
"""Model adinin altinda gorunecek kisa satir. kayit: gecidin /router/catalog girdisi (yoksa None).
|
||
Takim -> uyeleri; sirali yedek -> sirasi; tek model -> saglayicisi ve adindan okunan ozellikleri.
|
||
bilgi (pencere.coz sonucu): Codex'te isi etkileyen iki uyari one yazilir: kucuk baglam ("32K")
|
||
ve arac cagiramayan model ("araçsız")."""
|
||
if tur == "combo":
|
||
topoloji = (kayit or {}).get("topology") or {}
|
||
uyeler = topoloji.get("members") or []
|
||
if topoloji.get("strategy") == "round-robin":
|
||
return f"Dönüşümlü: {len(uyeler)} üye"
|
||
takim = topoloji if topoloji.get("strategy") == "fusion" else None
|
||
if takim is None and uyeler and isinstance(uyeler[0], dict) and uyeler[0].get("strategy") == "fusion":
|
||
takim = uyeler[0]
|
||
if takim is not None:
|
||
return _sigdir("Takım: ", [kisa_model(u.get("model")) for u in takim.get("members") or []], "+")
|
||
if uyeler:
|
||
return _sigdir("Sıralı: ", _yapraklar(topoloji), ">")
|
||
return "Takım"
|
||
kisa = gorunen_ad(kimlik, ad, "model")
|
||
kod = kisa.split("/")[0].lower() if "/" in kisa else (kimlik or "").split("-")[0].lower()
|
||
parcalar = [_SAGLAYICI.get(kod, kod) or "Tek model"]
|
||
if bilgi and bilgi.get("pencere") and bilgi["pencere"] < KUCUK_PENCERE:
|
||
parcalar.append(pencere.kisa(bilgi["pencere"]))
|
||
if bilgi and bilgi.get("arac") is False:
|
||
parcalar.append("araçsız")
|
||
if _SANSURSUZ.search(kisa) or _SANSURSUZ.search(kimlik or ""):
|
||
parcalar.append("sansürsüz")
|
||
if "thinking" in (kisa + " " + (kimlik or "")).lower():
|
||
parcalar.append("düşünen")
|
||
if len(parcalar) == 1:
|
||
parcalar.insert(0, "Tek model")
|
||
while len(parcalar) > 1 and len(" · ".join(parcalar)) > ACIKLAMA_SINIRI:
|
||
parcalar.pop() # sona yazilan en az onemli ozellik duser; kelime ortasindan kesilmez
|
||
return " · ".join(parcalar)[:ACIKLAMA_SINIRI]
|
||
|
||
|
||
def yukle(ua="AstraBridge/1.0 (codex)", timeout=30, home=None, ag=True):
|
||
"""Donus: (durum, base_url, anahtar, girdiler). durum: 'kapali' | 'anahtar-yok' | 'tamam'.
|
||
Ag hatasinda istisna atar; cagiran son listeyi korur. home: koprunun veri klasoru (pencere onbellegi).
|
||
Her girdi "pencere" (ilan edilecek baglam penceresi), "gorsel" ve "arac" alanlarini tasir."""
|
||
ayar = ayar_oku()
|
||
if not ayar["enabled"]:
|
||
return "kapali", "", "", []
|
||
anahtar = anahtar_oku()
|
||
if not anahtar:
|
||
return "anahtar-yok", "", "", []
|
||
base, varsayilan, adlar = ide_yapilandirma()
|
||
req = urllib.request.Request(base + "/models", headers={"Authorization": f"Bearer {anahtar}", "User-Agent": ua})
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
data = json.load(resp).get("data")
|
||
if not isinstance(data, list) or not data:
|
||
raise ValueError("Gecit bos veya gecersiz model katalogu dondurdu")
|
||
req = urllib.request.Request(base + "/router/catalog", headers={"Authorization": f"Bearer {anahtar}", "User-Agent": ua})
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
metadata = json.load(resp).get("models")
|
||
if not isinstance(metadata, list) or not metadata:
|
||
raise ValueError("Gecit model turlerini dondurmedi")
|
||
# Bu ucta uye listesinde olmayan ic kayitlar da bulunur. Yalniz tur bilgisi alinir;
|
||
# modele erisim listesi her zaman /models yanitidir.
|
||
turler = {m["id"]: m["kind"] for m in metadata if m.get("id") and m.get("kind") in ("combo", "model")}
|
||
girdiler = sec(data, adlar + [varsayilan], ayar["kapsam"], turler)
|
||
if not girdiler:
|
||
raise ValueError("Gecitte kullanilabilir OmniRoute sohbet modeli yok")
|
||
kayitlar = {m["id"]: m for m in metadata if m.get("id")}
|
||
nano = pencere.nano_yukle(home, ua, ag=ag) # ag=False: acilista yalniz onbellek
|
||
ogr = pencere.ogrenilen(home)
|
||
aracsiz = pencere.aracsizlar(home)
|
||
for g in girdiler:
|
||
bilgi = pencere.coz(g["id"], g["kind"], g.get("yol"), kayitlar.get(g["id"]), nano, ogr, aracsiz)
|
||
g["pencere"], g["gorsel"], g["arac"], g["pencere_kaynak"] = (bilgi["pencere"], bilgi["gorsel"],
|
||
bilgi["arac"], bilgi["kaynak"])
|
||
g["aciklama"] = aciklama(g["id"], g["ad"], g["kind"], kayitlar.get(g["id"]), bilgi)
|
||
return "tamam", base, anahtar, girdiler
|