From a46ff5101933bf8adbafda469d582329f21e100f Mon Sep 17 00:00:00 2001 From: yilsem Date: Sun, 11 Oct 2026 01:58:58 +0300 Subject: [PATCH] feat(codex-bridge): natural model command, spelled-out names, any gateway model, clear miss Live voice chat 01a12804: the user said "Seed iki nokta bir turbo yap" (no "modeli", name spelled out) and the model was in the gateway but not in Codex's list; the main model then promised the bridge would do it. A sentence ending in a command verb whose name fits exactly one model is now a command, single letters are joined, a name missing from Codex's list is looked up in the gateway's full model list, and an explicit command whose name fits nothing is answered by the bridge. Co-Authored-By: Claude Opus 5.5 --- apps/desktop/assets/codex-bridge/bridge.py | 36 ++++++++++++++++- .../assets/codex-bridge/model_gecis.py | 39 +++++++++++++++++-- .../assets/codex-bridge/test_model_gecis.py | 22 +++++++++++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index 1eef550..7265e98 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -773,6 +773,31 @@ def bilinen_modeller(): if m.get("slug") and m["slug"] not in gorulen] return liste +_GECIT = {"zaman": 0.0, "liste": []} + +def gecit_modelleri(): + """AITURK gecidindeki BUTUN modeller (Codex listesinde gorunmeyenler dahil), "ide/" olarak. Soylenen ad + Codex listesinde yoksa buraya bakilir (olcum 11 Ekim 2026: "Seed 2.1 Turbo" gecitte vardi, listede yoktu). + 10 dakika bellekte tutulur; alinamazsa bos liste.""" + simdi = time.time() + if simdi - _GECIT["zaman"] < 600: + return _GECIT["liste"] + _GECIT["zaman"] = simdi + rota = ROUTES.get("aiturk-ide") + if not rota: + _GECIT["liste"] = [] + return [] + try: + adres = rota["url"].rsplit("/", 1)[0] + "/models" + istek = urllib.request.Request(adres, headers={"Authorization": f"Bearer {rota['key']}", "User-Agent": UA}) + with urllib.request.urlopen(istek, timeout=6) as yanit: + veri = json.loads(yanit.read()) + _GECIT["liste"] = [("ide/" + m["id"], m["id"]) for m in veri.get("data", []) if isinstance(m, dict) and m.get("id")] + except Exception as e: + log(f"gecit model listesi alinamadi: {type(e).__name__}: {e}") + _GECIT["liste"] = [] + return _GECIT["liste"] + def model_adi(slug): return next((ad for kimlik, ad in bilinen_modeller() if kimlik == slug), None) or slug @@ -1156,7 +1181,16 @@ class H(http.server.BaseHTTPRequestHandler): return f"Sohbetin modeli şu an {model_adi(etkin)}. " + model_gecis.NASIL sonuc = model_gecis.eslestir(komut["ad"], bilinen_modeller()) if not sonuc: - return None # ad hicbir modele benzemiyor: komut degil, modele gider + sonuc = model_gecis.eslestir(komut["ad"], gecit_modelleri()) # Codex listesinde olmayan gecit modeli + if not komut.get("kesin", True) and not (sonuc and "slug" in sonuc): + return None # "modeli" denmemis ve ad tek modele oturmuyor: komut degil, modele gider + if not sonuc: + if ortada: + return None + # Acik komut ("modeli ... yap") ama ad bulunamadi: model "kopru yapacak" diye yanlis soz vermesin. + log(f"[{n} {gelen}] model komutu: ad bulunamadi {komut['ad']}") + return (f"\"{' '.join(komut['ad'])}\" adına uyan bir model bulamadım, model değişmedi; sohbetin modeli hâlâ " + f"{model_adi(etkin)}. Adını biraz farklı söyler misin?") if "adaylar" in sonuc: soru = ("Hangi modeli istediğini tek modele bağlayamadım, model değişmedi. En yakınlar: " + ", ".join(sonuc["adaylar"]) + ". Adını biraz daha tam söyler misin?") diff --git a/apps/desktop/assets/codex-bridge/model_gecis.py b/apps/desktop/assets/codex-bridge/model_gecis.py index 36f4e39..848a8d8 100644 --- a/apps/desktop/assets/codex-bridge/model_gecis.py +++ b/apps/desktop/assets/codex-bridge/model_gecis.py @@ -120,11 +120,42 @@ def _sayilari_cevir(kelimeler): return cikti +DOLGU = {"evet", "tamam", "hadi", "peki", "tabii", "hayir", "yani", "sey", "ee", "eee", "hmm", "olur", "simdi"} + + +def _harfleri_birlestir(kelimeler): + """Harf harf soylenen ad tek sozcuk olur: "s e e d iki nokta bir turbo" -> "seed iki nokta bir turbo".""" + cikti, harfler = [], [] + for k in kelimeler + [""]: + if len(k) == 1 and k.isalpha(): + harfler.append(k) + continue + if len(harfler) >= 3: + cikti.append("".join(harfler)) + else: + cikti.extend(harfler) + harfler = [] + if k: + cikti.append(k) + # Ad hem heceleyerek hem duz soylendiyse ("seed s e e d ...") tekrar atilir. + return [k for i, k in enumerate(cikti) if i == 0 or k != cikti[i - 1]] + + 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): + kelimeler = _harfleri_birlestir(re.findall(r"[a-z0-9]+", duz(metin))) + if not kelimeler or len(kelimeler) > EN_COK_KELIME: return None + if not any(k.startswith("model") for k in kelimeler): + # "modeli" denmeden soylenen komut ("Seed iki nokta bir turbo yap", "gemini flash low'a gec"): yalniz cumle + # bir komut fiiliyle bitiyorsa ve ad bir modele TAM uyuyorsa komut sayilir (kesin=False: cagiran, ad + # bulunamazsa mesaji komut saymaz, modele birakir). + if any(k.startswith(OLUMSUZ) for k in kelimeler) or kelimeler[-1] not in FIILLER or len(kelimeler) > 9: + return None + kelimeler = _sayilari_cevir(kelimeler) + ad = [TAKMA.get(k, k) for k in kelimeler[:-1] if k not in ATLA and k not in DOLGU] + ad = [p for k in ad for p in re.findall(r"[a-z]+|\d+", k)] + return {"tur": "gec", "ad": ad, "kesin": False} if len(ad) >= 2 else None if len(kelimeler) <= 8 and any(k in SORU for k in kelimeler): return {"tur": "sor"} if any(k in SORU_ISARETI for k in kelimeler) else None if any(k.startswith(OLUMSUZ) for k in kelimeler): @@ -135,10 +166,10 @@ def komut_coz(metin): if all(k in DEVAM_EKI for k in kelimeler[son_fiil + 1:]): kelimeler = kelimeler[:son_fiil + 1] kelimeler = _sayilari_cevir(kelimeler) - ad = [TAKMA.get(k, k) for k in kelimeler if k not in ATLA and k not in fiil] + ad = [TAKMA.get(k, k) for k in kelimeler if k not in ATLA and k not in fiil and k not in DOLGU] ad = [p for k in ad for p in re.findall(r"[a-z]+|\d+", k)] # Ad soylenmemis ("modeli değiştir", "başka modele geç"): degisiklik yok, nasil soylenecegi anlatilir. - return {"tur": "gec", "ad": ad} if ad else {"tur": "nasil"} + return {"tur": "gec", "ad": ad, "kesin": True} if ad else {"tur": "nasil"} return None diff --git a/apps/desktop/assets/codex-bridge/test_model_gecis.py b/apps/desktop/assets/codex-bridge/test_model_gecis.py index 65a9d6b..9bddabe 100644 --- a/apps/desktop/assets/codex-bridge/test_model_gecis.py +++ b/apps/desktop/assets/codex-bridge/test_model_gecis.py @@ -112,6 +112,28 @@ class Bilgi(unittest.TestCase): self.assertIsNone(g.komut_coz("modeli değiştirme")) +class Dogal(unittest.TestCase): + GECIT = [("ide/9router/ModelARK/dola-seed-2-1-turbo-260628", "9router/ModelARK/dola-seed-2-1-turbo-260628"), + ("ide/9router/ModelARK/seed-2-0-pro-260328", "9router/ModelARK/seed-2-0-pro-260328")] + + def test_command_without_the_word_model_and_a_spelled_out_name(self): + for text in ("Seed iki nokta bir turbo yap", "Evet. Seed, S E E D iki nokta bir turbo yap"): + command = g.komut_coz(text) + self.assertEqual((command["ad"], command["kesin"]), (["seed", "2", "1", "turbo"], False), text) + self.assertEqual(g.eslestir(command["ad"], self.GECIT)["slug"], "ide/9router/ModelARK/dola-seed-2-1-turbo-260628") + self.assertTrue(g.komut_coz("Modeli Seed iki nokta bir turbo yap")["kesin"]) + self.assertEqual(g.eslestir(g.komut_coz("gemini flash low'a geç")["ad"], MODELS)["slug"], + "ide/9router-ag-gemini-3.8-flash-low") + + def test_ordinary_requests_ending_in_a_verb_are_not_commands(self): + for text in ("test yap", "kahve yap", "yapma"): + self.assertIsNone(g.komut_coz(text), text) + for text in ("şu dosyayı düzelt ve commit yap", "rapor hazırla gemini flash low ile karşılaştırma yap"): + command = g.komut_coz(text) + self.assertFalse(command["kesin"]) + self.assertFalse((g.eslestir(command["ad"], MODELS) or {}).get("slug"), text) + + class Yon(unittest.TestCase): def test_route_lasts_until_the_user_picks_another_model_in_the_list(self): with tempfile.TemporaryDirectory() as home: