"""Sesle ya da yaziyla "modeli sunu yap" komutu ve sohbet basina model yonlendirmesi (10 Ekim 2026). Olcum (sesli sohbet 01a12780): telefonda secilen model bilgisayara yalnizca YENI TUR baslatan yazili mesajla ulasiyor. Calisan tura eklenen mesaj (nokta dahil) secimi tasimiyor; dort denemenin ucunde secim kayboldu. Codex'in pencereler arasi kanali da sohbet bir pencerede acik degilse istegi kabul etmiyor. Bu yuzden degisiklik koprude yapilir: kullanici "modeli gemini flash low yap" der (sesli asistan bunu her soz gibi ana modele iletir), kopru cumleyi tanir, o sohbetin isteklerini secilen modele yonlendirir ve cevabi kendisi verir. - Model bostayken: kopru yerel cevap verir ("Sohbetin modeli artik ..."). - Model calisirken (tur ortasi): komut mesaji kisa bir devir notuna cevrilir ve AYNI tur yeni modelle surer; yarim kalan is kesilmez, yeni model kaldigi yerden devam eder. - Kullanici sonradan listeden baska model secerse (Codex'in gonderdigi ad degisir) yonlendirme kendiliginden kalkar. Ad bulunamazsa mesaj komut sayilmaz, modele oldugu gibi gider. Birden cok aday varsa degisiklik yapilmaz, adaylar sorulur. """ import json import os import re import threading import time DOSYA = "model-yonlendirme.json" OMUR_SN = 7 * 24 * 3600 EN_COK_KAYIT = 200 EN_COK_KELIME = 14 _KILIT = threading.Lock() _TR = str.maketrans("çğıöşüâîû", "cgiosuaiu") FIILLER = ("gec", "degis", "yap", "sec", "kullan", "cevir", "olsun", "ayarla") SORU = ("hangi", "hangisi", "kim", "kimdir") ATLA = {"model", "modeli", "modelini", "modele", "modeline", "modelin", "modelle", "modeller", "su", "bu", "o", "bir", "olarak", "ile", "lutfen", "artik", "ana", "sohbet", "sohbetin", "chat", "chatin", "chatte", "simdi", "hadi", "bana", "icin", "de", "da", "ve", "mi", "misin", "musun", "mu", "yeni", "diger", "baska", "olan", "codex", "ya", "e", "a", "i", "u", "yi", "ye", "na", "ne", "nu", "seni", "sen", "biz", "onu", "ona"} # Konusmadan yaziya ceviride sik gorulen yazimlar -> model adindaki parca TAKMA = {"cloud": "claude", "klod": "claude", "klaud": "claude", "claud": "claude", "opus": "opus", "opüs": "opus", "cemini": "gemini", "jemini": "gemini", "cemin": "gemini", "flas": "flash", "fles": "flash", "kuven": "qwen", "kiven": "qwen", "kuen": "qwen", "kven": "qwen", "kodeks": "codex", "dusuk": "low", "yuksek": "high", "orta": "medium", "sonet": "sonnet", "haiku": "haiku", "sansursuz": "uncensored", "ansansird": "uncensored", "dusunen": "thinking", "dusunmeli": "thinking", "ciptii": "gpt", "cipiti": "gpt", "gipiti": "gpt"} # Listede kisa yazilan saglayici -> konusurken soylenen ad SAGLAYICI = {"cc": ("claude",), "agy": ("gemini", "antigravity"), "ag": ("gemini", "antigravity"), "cx": ("codex", "gpt")} def duz(metin): return str(metin or "").lower().translate(_TR) def parcala(metin): """Harf ve rakam obeklerine ayirir: "agy/gemini-3.8-flash-low" -> agy gemini 3 8 flash low.""" return [TAKMA.get(p, p) for p in re.findall(r"[a-z]+|\d+", duz(metin))] def son_sira(girdi): """Son kullanici mesajinin sirasi. Arkasindaki talimat (developer/system) mesajlari atlanir: kancalar kullanici mesajinin ardina baglam ekler (olcum 10 Ekim 2026). Baska bir oge varsa -1.""" if not isinstance(girdi, list): return -1 for i in range(len(girdi) - 1, -1, -1): oge = girdi[i] if isinstance(oge, dict) and oge.get("type", "message") == "message": if oge.get("role") == "user": return i if oge.get("role") in ("developer", "system"): continue return -1 return -1 def son_kullanici_metni(req): """(metin, sira, sesli). Son girdi kullanici metin mesaji degilse (None, -1, False). Sesli aktarimda () yalnizca icindeki soz alinir.""" girdi = req.get("input") sira = son_sira(girdi) if sira < 0: return None, -1, False son = girdi[sira] icerik = son.get("content") if isinstance(icerik, str): metin = icerik elif isinstance(icerik, list) and icerik and all(isinstance(p, dict) and p.get("type") in ("input_text", "text") for p in icerik): metin = "".join(str(p.get("text") or "") for p in icerik) else: return None, -1, False m = re.search(r".*?(.*?)", metin, re.S) if m: return m.group(1).strip(), sira, True if "<" in metin and ">" in metin: return None, -1, False # baska bir sarmalli sistem mesaji; komut aranmaz return metin.strip(), sira, False def komut_coz(metin): """None | {"tur": "gec", "ad": [parcalar]} | {"tur": "sor"}.""" kelimeler = re.findall(r"[a-z0-9]+", duz(metin)) if not kelimeler or len(kelimeler) > EN_COK_KELIME or not any(k.startswith("model") for k in kelimeler): return None if len(kelimeler) <= 8 and any(k in SORU for k in kelimeler): return {"tur": "sor"} fiil = [k for k in kelimeler if k.startswith(FIILLER) and not k.startswith("model")] if fiil: ad = [TAKMA.get(k, k) for k in kelimeler if k not in ATLA and k not in fiil] ad = [p for k in ad for p in re.findall(r"[a-z]+|\d+", k)] return {"tur": "gec", "ad": ad} if ad else None return None def _uyar(sorgu, parca): """Turkce ek almis soz ("geminiye", "opusa") model parcasiyla baslar.""" if sorgu == parca: return True return len(parca) >= 3 and sorgu.startswith(parca) and len(sorgu) - len(parca) <= 4 and not sorgu.isdigit() def eslestir(ad, modeller): """ad: sorgu parcalari; modeller: [(slug, gorunen ad)]. Doner: {"slug", "ad"} (tek aday) | {"adaylar": [gorunen adlar]} (belirsiz) | None (ad bir modele benzemiyor).""" if not ad: return None puanli = [] for slug, gorunen in modeller: parcalar = parcala(gorunen) or parcala(slug) kume = set(parcalar) | set(parcala(slug)) for kisa, uzunlar in SAGLAYICI.items(): if kisa in kume: kume.update(uzunlar) tutan = sum(1 for s in ad if any(_uyar(s, p) for p in kume)) harf = sum(1 for s in ad if not s.isdigit() and any(_uyar(s, p) for p in kume)) if harf: # yalniz rakam tutmasi ("3", "5") esleme sayilmaz puanli.append((tutan, -len(parcalar), slug, gorunen)) if not puanli: return None en = max(p[0] for p in puanli) if en < len(ad) and en < max(2, (len(ad) * 2 + 2) // 3): return None # sozun cogu hicbir modele uymuyor: komut degil iyi = sorted((p for p in puanli if p[0] == en), key=lambda p: (-p[1], p[3])) if len(iyi) == 1 or iyi[0][1] != iyi[1][1]: return {"slug": iyi[0][2], "ad": iyi[0][3]} return {"adaylar": [p[3] for p in iyi[:4]]} _ARAC = {"function_call", "function_call_output", "custom_tool_call", "custom_tool_call_output", "reasoning", "local_shell_call", "local_shell_call_output", "tool_search_call", "tool_search_output"} def tur_ortasi(req, sira): """Mesaj calisan bir turun icine mi dustu? Geriye dogru, kullanici/talimat mesajlarini atlayarak bakilir: once arac ogesi ya da ara (commentary) mesaj gelirse tur suruyordur; once son cevap gelirse tur yeni baslamistir.""" girdi = req.get("input") or [] for oge in reversed(girdi[:max(sira, 0)]): if not isinstance(oge, dict): continue tur = oge.get("type", "message") if tur in _ARAC: return True if tur == "message": if oge.get("role") == "assistant": return oge.get("phase") == "commentary" continue return False def metni_degistir(req, sira, yeni): """sira'daki kullanici mesajinin metnini yeni ile degistirir (sesli aktarimda yalnizca icini).""" oge = req["input"][sira] icerik = oge.get("content") eski = icerik if isinstance(icerik, str) else "".join(str(p.get("text") or "") for p in icerik) if "" in eski: metin = re.sub(r"().*?()", lambda m: m.group(1) + yeni + m.group(2), eski, count=1, flags=re.S) else: metin = yeni oge["content"] = metin if isinstance(icerik, str) else [{"type": icerik[0].get("type", "input_text"), "text": metin}] def gecmisi_temizle(req, modeller): """Gecmisteki (son mesaj disindaki) islenmis "modeli X yap" komutlarini kisa nota cevirir; yoksa sonraki model ayni cumleyi yeniden gorup "modeli ben degistiremem" diye cevap veriyor (olcum 11 Ekim 2026). Doner: degistirilen mesaj sayisi.""" girdi = req.get("input") if not isinstance(girdi, list): return 0 son = son_sira(girdi) n = 0 for i, oge in enumerate(girdi): if i == son or not (isinstance(oge, dict) and oge.get("type", "message") == "message" and oge.get("role") == "user"): continue metin, _, _ = son_kullanici_metni({"input": [oge]}) if not metin or "model" not in duz(metin) or metin.startswith("[Köprü notu"): continue komut = komut_coz(metin) sonuc = eslestir(komut["ad"], modeller) if komut and komut["tur"] == "gec" else None if sonuc and "slug" in sonuc: metni_degistir(req, i, f"[Köprü notu: kullanıcı burada sohbetin modelini {sonuc['ad']} olarak değiştirdi; " "değişiklik köprü tarafından yapıldı, senin bir şey yapman gerekmiyor.]") n += 1 return n def devir_notu(yeni_ad, eski_ad): return (f"[Köprü notu: kullanıcı bu sohbetin modelini değiştirdi. Önceki model {eski_ad} idi; şu andan itibaren " f"cevap veren model sensin ({yeni_ad}). Yarım kalan işi kaldığı yerden sürdür, baştan başlama. " f"Cevabının ilk cümlesinde kısaca \"{yeni_ad} olarak devam ediyorum\" de.]") class Yonlendirme: """sohbet anahtari -> {"gorulen": Codex'in gonderdigi ad, "hedef": yonlenen ad, "zaman"}; diske yazilir.""" def __init__(self, home): self.yol = os.path.join(home, DOSYA) if home else None self.veri = {} if self.yol: try: with open(self.yol, encoding="utf-8") as f: okunan = json.load(f) if isinstance(okunan, dict): self.veri = {k: v for k, v in okunan.items() if isinstance(v, dict) and v.get("hedef")} except (OSError, ValueError): pass def _yaz(self): if not self.yol: return try: gecici = self.yol + ".tmp" with open(gecici, "w", encoding="utf-8") as f: json.dump(self.veri, f, ensure_ascii=False) os.replace(gecici, self.yol) except OSError: pass def al(self, sohbet, gelen, simdi=None): """Bu sohbet icin yonlenen ad; yoksa None. Codex baska bir ad gonderiyorsa (kullanici listeden yeni model secti) ya da kayit eskidiyse yonlendirme silinir.""" simdi = simdi or time.time() with _KILIT: k = self.veri.get(sohbet) if not k: return None if k.get("gorulen") != gelen or simdi - float(k.get("zaman") or 0) > OMUR_SN: del self.veri[sohbet] self._yaz() return None return k["hedef"] def koy(self, sohbet, gelen, hedef, simdi=None): simdi = simdi or time.time() with _KILIT: if hedef == gelen: self.veri.pop(sohbet, None) else: self.veri[sohbet] = {"gorulen": gelen, "hedef": hedef, "zaman": simdi} if len(self.veri) > EN_COK_KAYIT: for eski in sorted(self.veri, key=lambda a: self.veri[a].get("zaman") or 0)[:-EN_COK_KAYIT]: del self.veri[eski] self._yaz()