Codex only received combos and the models named in the IDE config, so none of the models behind the providers added to 9Router showed up. Those single models are now always added; 9Router's combos stay hidden. The copy of the same model that comes through OmniRoute is still added only when the IDE config names it, so each model is listed once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDGW1LiKcDUZYUdKBoWYW
167 lines
7.6 KiB
Python
167 lines
7.6 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
|
|
|
|
|
|
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."""
|
|
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 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: (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"])
|