Files
aiturk-hermes-ide/apps/desktop/assets/codex-bridge/ide_catalog.py
T

177 lines
8.0 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" : IDE ayarinda tanimli modeller + 9Router'a eklenmis saglayicilarin modelleri +
adlandirilmis combolar (varsayilan)
kapsam "hepsi" : gecitteki butun sohbet modelleri (1000+; Codex listesi cok buyur)
"""
import json, os, re, urllib.request
# 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
DOKUZ_ISARET = "9R · "
def _isaret(ad):
"""Gecidin dogrudan 9Router'dan gelen tekil modele koydugu kisa isaret korunur (kullanici karari,
9 Ekim 2026: "9Router" yazisi uzun, hic yazmamak da sorun; "9R" olarak kisa gorunsun)."""
return DOKUZ_ISARET if (ad or "").startswith(DOKUZ_ISARET) else ""
def kisa_ad(ad):
"""Codex secicisinde gorunecek ad: yalniz kullanicinin ekledigi saglayici + model adi
(kullanici istegi, 9 Ekim 2026). "OmniRoute · " / "9Router · " gibi gecit onekleri ve aradaki
saglayici halkalari gosterilmez: "nano/qwen/qwen3.8-27b" -> "nano/qwen3.8-27b",
"OmniRoute · ag/gemini-3.8-flash" -> "ag/gemini-3.8-flash", combo adi oldugu gibi kalir."""
on = _isaret(ad)
ad = (ad or "").split(" · ")[-1].strip()
parcalar = [p for p in ad.split("/") if p]
if len(parcalar) <= 2:
return on + ("/".join(parcalar) or ad)
return on + parcalar[0] + "/" + parcalar[-1]
def adlandir(girdiler, dolu=()):
"""[(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."""
kisa = {k: kisa_ad(a) for k, a in girdiler}
tam = {k: _isaret(a) + (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"):
"""Gecit katalogundan Codex'e eklenecek girdiler: [{'id','ad'}], sirali ve tekrarsiz.
Yalniz sohbet modelleri; resim/ses/video/embedding uretenler eklenmez."""
tanimli = {a.lower() for a in tanimli if a}
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
kisa = gorunen.split(" · ")[-1].strip()
combo = "/" not in kisa # saglayici/model bicimi degil: adlandirilmis combo ya da takma ad
# 9Router'dan gelen combolar Codex'te gosterilmez (kullanici karari, 9 Ekim 2026): dogrudan
# 9Router kaynagi ve gecidin "9router" saglayicisi uzerinden gelen kopyalari.
if combo and (ts.get("source") == "9router" or mid.lower().startswith("9router-")):
continue
# 9Router'a eklenmis saglayicilarin tekil modelleri her zaman eklenir (kullanici karari, 9 Ekim 2026:
# "9Router'in combosu olmasin dedim, 9Router'in modeli olsun"). Ayni modelin OmniRoute uzerinden
# gelen kopyasi yalniz IDE ayarinda tanimliysa eklenir; boylece her model listede bir kez durur.
dokuz_model = not combo and ts.get("source") == "9router"
if kapsam != "hepsi" and not combo and not dokuz_model and kisa.lower() not in tanimli and mid.lower() not in tanimli:
continue
gorulen.add(mid)
secilen.append({"id": mid, "ad": gorunen, "sira": ts.get("order") or 0})
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"]} for s in secilen]
def yukle(ua="AstraBridge/1.0 (codex)", timeout=30):
"""Donus: (durum, base_url, anahtar, girdiler). durum: 'kapali' | 'anahtar-yok' | 'tamam'.
Ag hatasinda istisna atar; cagiran son listeyi korur."""
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})
data = json.load(urllib.request.urlopen(req, timeout=timeout)).get("data", [])
return "tamam", base, anahtar, sec(data, adlar + [varsayilan], ayar["kapsam"])