"""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