Files
aiturk-hermes-ide/apps/desktop/assets/codex-bridge/ide_catalog.py
T
yilsemandClaude Opus 5.5 acf88f8314 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>
2026-10-10 22:04:58 +03:00

353 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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