diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index 9f40f0a..8e790d8 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -790,6 +790,23 @@ def bilinen_modeller(): if m.get("slug") and m["slug"] not in gorulen] return liste +def ek_model_mu(slug): + """Bizim sonradan ekledigimiz model mi (ide/..., 9r/..., bridge.json'da tanimli)? Degilse Codex'in kendi + (abonelik) modelidir ve kopru ona DOKUNMAZ. Kullanici karari (11 Ekim 2026): "Codex'in modelleri bir tarafta. + Bizim sonradan eklediklerimiz diger tarafta. Bizim sonradan eklediklerimiz kopru kullaniyor. Codex'in + modelleri kopru kullanmiyor." """ + slug = str(slug or "") + onek = (CFG.get("nine_router_catalog") or {}).get("prefix", "9r/") + return bool(slug) and (MODEL_MAP.get(slug) is not None or slug.startswith("ide/") or bool(onek and slug.startswith(onek))) + + +def yonlendirilen(sohbet, gelen): + """Sohbetin sesle secilmis ek modeli; yoksa None. Codex'in kendi modeline giden eski kayit yok sayilir: + kendi modellerine sesle gecilmez, ekrandaki seciciden secilir.""" + hedef = YONLENDIRME.al(sohbet, gelen) + return hedef if hedef and ek_model_mu(hedef) else None + + _GECIT = {"zaman": 0.0, "liste": []} def gecit_modelleri(): @@ -1185,7 +1202,7 @@ class H(http.server.BaseHTTPRequestHandler): komut = model_gecis.komut_coz(metin) if metin else None if not komut: return None - etkin = YONLENDIRME.al(sohbet, gelen) or gelen + etkin = yonlendirilen(sohbet, gelen) or gelen ortada = model_gecis.tur_ortasi(req, sira) if komut["tur"] == "sor": if ortada: @@ -1197,9 +1214,27 @@ class H(http.server.BaseHTTPRequestHandler): return None log(f"[{n} {gelen}] adsiz model komutu: nasil soylenecegi anlatildi") return f"Sohbetin modeli şu an {model_adi(etkin)}. " + model_gecis.NASIL - sonuc = model_gecis.eslestir(komut["ad"], bilinen_modeller()) + # Sesle gecis yalniz bizim ekledigimiz modeller arasindadir; Codex'in kendi modelleri aday degildir + # (kullanici karari, 11 Ekim 2026). Iki taraf arasinda gecis: ekrandaki seciciden model secilir, "." yazilir. + bilinen = bilinen_modeller() + sonuc = model_gecis.eslestir(komut["ad"], [(s, a) for s, a in bilinen if ek_model_mu(s)]) if not sonuc: sonuc = model_gecis.eslestir(komut["ad"], gecit_modelleri()) # Codex listesinde olmayan gecit modeli + if not sonuc: + ozgun = model_gecis.eslestir(komut["ad"], [(s, a) for s, a in bilinen if not ek_model_mu(s)]) + if ozgun and (komut.get("kesin", True) or "slug" in ozgun): + kim = f"{ozgun['ad']} Codex'in kendi modeli" if "ad" in ozgun else "Bu ad Codex'in kendi modellerine uyuyor" + ileti = (f"{kim}; Codex'in kendi modellerine sesle geçilmiyor, model değişmedi. Geçmek için ekrandaki " + f"model seçiciden o modeli seç ve tek başına \".\" yaz. Sohbetin modeli hâlâ {model_adi(etkin)}.") + log(f"[{n} {gelen}] model komutu: Codex'in kendi modeli istendi {komut['ad']}, gecilmedi") + if not ortada: + return ileti + if not komut.get("kesin", True): + return None + model_gecis.metni_degistir(req, sira, "[Köprü notu: kullanıcı Codex'in kendi modeline geçmek istedi; bu sesle " + "yapılamıyor, model değişmedi. İşine devam et; cevabının sonunda şunu ilet: " + ileti + "]") + self.komut_govdeyi_degistirdi = True + return None 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: @@ -1236,6 +1271,32 @@ class H(http.server.BaseHTTPRequestHandler): return (f"Sohbetin modeli artık {sonuc['ad']}. Önceki model {model_adi(etkin)} idi. " f"Bundan sonraki işleri {sonuc['ad']} yapacak; hangi model diye sorulursa bunu söyle.{donus}") + def _ozgun(self, n, path, body, req, slug): + """Codex'in kendi (abonelik) modeli. Kullanici karari (11 Ekim 2026): "Codex kendi modelini kullanirken + kopruyle bir bagi olmamali. Codex kendi modelinden kendi modeline gectigi zaman kopruyle bir bagi olmamali. + Codex'in kendi modeline dokunulmamali." Istek chatgpt.com'a OLDUGU GIBI gider: model komutu, nokta + karsilama, bilgi notu, is hatti talimati, yarim cevap notu ve sikistirmaya karisma YOK. Sikistirmayi Codex + kendisi OpenAI'de yapar (olcum 11 Ekim 2026: kopru ozgun modelde ozeti kendi uretmeye calisip dort kez + "ozet uretilemedi" verdi, Codex "Error running remote compact task" ile takildi). + + Tek dokunus koprunun KENDI izleridir ve yalniz sohbette daha once ek model kullanildiysa olur: ek modelin + urettigi yabanci kimlikler ve koprunun urettigi duz metin ozet ogeleri. Bunlar kalirsa chatgpt.com istegi + reddeder ("Item ... not found"). Iz yoksa govde bayt bayt ayni kalir.""" + fixed = atilan = acilan = 0 + try: + fixed = fix_tool_ids(req) + atilan = drop_foreign_items(req) + if (CFG.get("ozet_kopru") or {}).get("enabled", False): + acilan = ozet_kopru.ozetleri_ac(req, yabanci_hedef=False)["acilan"] + if fixed or atilan or acilan: + body = json.dumps(req, ensure_ascii=False).encode("utf-8") + log(f"[{n} CHATGPT {slug}] kopru izleri temizlendi: {fixed} arac kimligi, {atilan} yabanci oge, " + f"{acilan} kopru ozeti") + except Exception as e: + fixed = atilan = acilan = 0 # govde degismeden gider + log(f"[{n} CHATGPT {slug}] kopru izi temizligi atlandi, istek oldugu gibi gidiyor: {type(e).__name__}: {e}") + return self._passthrough(n, path, body, tagname=f"CHATGPT {slug}", rewritten=bool(fixed or atilan or acilan)) + def _responses(self, n, path, body): try: req = json.loads(decompress(body, self.headers.get("Content-Encoding"))) @@ -1244,6 +1305,8 @@ class H(http.server.BaseHTTPRequestHandler): return self._send_json(400, {"error": {"message": f"bridge: govde cozulemedi: {e}"}}) slug = req.get("model", "") gelen = slug # Codex'in bu sohbet icin gonderdigi ad (listeden secilen model) + if not ek_model_mu(gelen): + return self._ozgun(n, path, body, req, slug) govde_degisti = False # istek govdesi kopruce degistirildi: abonelik yolunda yeniden yazilir once = len(req.get("input") or []), json.dumps((req.get("input") or [None])[-3:], ensure_ascii=False, sort_keys=True) sohbet = str(req.get("prompt_cache_key") or "") @@ -1259,11 +1322,11 @@ class H(http.server.BaseHTTPRequestHandler): if self.komut_govdeyi_degistirdi: govde_degisti = True # komut mesaji nota cevrildi: abonelik yolunda da degismis govde gider try: - if model_gecis.gecmisi_temizle(req, bilinen_modeller(), YONLENDIRME.al(sohbet, gelen) or gelen): + if model_gecis.gecmisi_temizle(req, bilinen_modeller(), yonlendirilen(sohbet, gelen) or gelen): govde_degisti = True except Exception as e: log(f"[{n} {gelen}] gecmis komut temizligi atlandi: {type(e).__name__}: {e}") - hedef = YONLENDIRME.al(sohbet, gelen) + hedef = yonlendirilen(sohbet, gelen) if hedef and hedef != gelen: req["model"] = slug = hedef log(f"[{n} {gelen}] sohbet yonlendirmesi: istek {hedef} modeline gidiyor") @@ -1333,53 +1396,14 @@ class H(http.server.BaseHTTPRequestHandler): # deyip duruyordu (olcum 11 Ekim 2026, sunucu gecisi sirasinda). Kullanici durumu duysun. if CFG.get("kesinti_mesaji", True): return self._yerel_yanit(gelen, "AITURK modellerine şu an ulaşılamıyor (AITURK sunucusu cevap vermiyor ya da " - "bağlantı kapalı); bu model şimdi cevap veremez. Beklemek istemezsen Codex'in kendi " - "modeline geç: \"modeli gpt 6 astra yap\" de. AITURK geri gelince tekrar " - "\"modeli … yap\" diyerek dönebilirsin.") + "bağlantı kapalı); bu model şimdi cevap veremez. Beklemek istemezsen ekrandaki model " + "seçiciden Codex'in kendi modellerinden birini seç ve tek başına \".\" yaz.") return self._send_json(503, {"error": {"message": "AITURK IDE Codex Baglama kapali ya da IDE oturumu bulunamadi; bu model su an kullanilamiyor."}}) if mapping is None: - # abonelik modeli: chatgpt.com'a oldugu gibi - fixed = fix_tool_ids(req) - atilan = drop_foreign_items(req) - degisti = bool(yarim_eklenen) or govde_degisti - ozet_akisi = None - if ozet_acik: - # Ozet bagdastirici (ozet_kopru.py; 9 Ekim 2026'da yalniz bu parca geri acildi): koprunun urettigi - # duz metin ozetleri OpenAI modeline de mesaj olarak gider. - try: - ob = ozet_kopru.ozetleri_ac(req, yabanci_hedef=False) - # kapsam "hepsi": OpenAI modelinde de ozeti kopru DUZ METIN urettirir; boylece ozet sonradan - # secilen her saglayicida okunur. kapsam "9router": OpenAI ozeti yerlesik (sifreli) kalir. - if ozet_cfg.get("kapsam", "hepsi") == "hepsi" and ozet_kopru.tetik_mi(req): - OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 8)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600)); OZET_FRENI.art_arda = int(ozet_cfg.get("dongu_art_arda", 3)) - if not OZET_FRENI.izin(str(req.get("prompt_cache_key") or slug), time.time()): - log(f"[{n} CHATGPT {slug}] ozet kopru: DONGU FRENI, ozet istegi reddedildi") - return self._send_json(400, {"error": {"message": ozet_kopru.DONGU_MESAJI % OZET_FRENI.sinir, - "type": "invalid_request_error", "code": "astrabridge_ozet_dongusu"}}) - girdi_bayt = len(json.dumps(req.get("input") or [], ensure_ascii=False).encode("utf-8")) - ozet_kopru.ozet_istegi_hazirla(req, ozet_cfg.get("istem")) - ozet_akisi = ozet_kopru.OzetAkisi(None, tohum=str(n), en_az=ozet_kopru.en_az_karakter(girdi_bayt, ozet_cfg)) - ob["acilan"] += 1 - log(f"[{n} CHATGPT {slug}] ozet kopru: sikistirma istegi duz metin ozet istegine cevrildi (girdi={len(req.get('input', []))} oge)") - except Exception as e: - ob = {"acilan": 0}; ozet_akisi = None - log(f"[{n} CHATGPT {slug}] ozet kopru atlandi: {type(e).__name__}: {e}") - if ob["acilan"]: - degisti = True - if ozet_akisi is None: - log(f"[{n} CHATGPT {slug}] ozet kopru: {ob['acilan']} kopru ozeti duz metne acildi") - if fixed or atilan or degisti: - body = json.dumps(req, ensure_ascii=False).encode("utf-8") - parcalar = [] - if fixed: parcalar.append(f"{fixed} arac kimligi duzeltildi") - if atilan: parcalar.append(f"{atilan} yabanci oge atildi") - if parcalar: log(f"[{n} CHATGPT {slug}] " + ", ".join(parcalar)) - if ozet_akisi is not None: - sonuc = self._passthrough(n, path, body, tagname=f"CHATGPT {slug}", rewritten=True, rewriter=ozet_akisi) - log(f"[{n} CHATGPT {slug}] ozet kopru: {ozet_akisi.sonuc_notu()}") - return sonuc - return self._passthrough(n, path, body, tagname=f"CHATGPT {slug}", - rewritten=bool(fixed or atilan or degisti), izle=izle) + # Codex'in kendi modelleri en basta _ozgun ile ayrildi; buraya yalniz rotasi cozulemeyen bir ek ad duser. + # Ek model chatgpt.com'a baska bir adla gonderilmez. + log(f"[{n} {slug}] ek modelin rotasi cozulemedi; istek gonderilmedi") + return self._send_json(503, {"error": {"message": "Bu model artik bu koprude tanimli degil; listeden baska bir model secin."}}) route_name, target, flat = mapping try: ozet_tetik = ozet_acik and ozet_kopru.tetik_mi(req) diff --git a/apps/desktop/assets/codex-bridge/model_gecis.py b/apps/desktop/assets/codex-bridge/model_gecis.py index 848a8d8..a8a9c48 100644 --- a/apps/desktop/assets/codex-bridge/model_gecis.py +++ b/apps/desktop/assets/codex-bridge/model_gecis.py @@ -294,9 +294,12 @@ BILGI = (BILGI_ISARETI + " Bu sohbetin modeli söylenerek değiştirilebilir: ku "(örnek: \"modeli gemini flash low yap\", \"modeli opus beş beş yap\") değişikliği aradaki köprü yapar; senin bir " "araç çağırman ya da ayar değiştirmen gerekmez. Kullanıcı modeli değiştirip değiştiremeyeceğini sorarsa " "\"değiştiremem\" deme; bu cümleyi söylemesinin yeterli olduğunu kısaca anlat. Hangi modelde olduğunu öğrenmek " - "için \"seçili model hangisi\" demesi yeter.") + "için \"seçili model hangisi\" demesi yeter. Codex'in kendi modellerine (listenin başındaki abonelik modelleri) " + "sesle geçilmez: kullanıcı onlardan birine geçmek isterse ekrandaki model seçiciden seçip tek başına \".\" " + "yazması gerektiğini söyle.") NASIL = ("Hangi modele geçeyim? Adını söylemen yeterli, örneğin: \"modeli gemini flash low yap\" ya da " - "\"modeli opus beş beş yap\". Şu anki modeli öğrenmek için \"seçili model hangisi\" de.") + "\"modeli opus beş beş yap\". Şu anki modeli öğrenmek için \"seçili model hangisi\" de. Codex'in kendi " + "modellerine sesle geçilmez; onlar için ekrandaki model seçiciden seçip tek başına \".\" yaz.") def bilgi_ekle(req): diff --git a/apps/desktop/assets/codex-bridge/test_access_programs.py b/apps/desktop/assets/codex-bridge/test_access_programs.py index 5e6b8df..d5dda6f 100644 --- a/apps/desktop/assets/codex-bridge/test_access_programs.py +++ b/apps/desktop/assets/codex-bridge/test_access_programs.py @@ -15,6 +15,8 @@ flatten = next(n for n in source.body if isinstance(n, ast.FunctionDef) and n.na claude_sonu = next(n for n in source.body if isinstance(n, ast.FunctionDef) and n.name == "claude_sonu_duzelt") handler = next(n for n in source.body if isinstance(n, ast.ClassDef) and n.name == "H") responses = next(n for n in handler.body if isinstance(n, ast.FunctionDef) and n.name == "_responses") +ozgun = next(n for n in handler.body if isinstance(n, ast.FunctionDef) and n.name == "_ozgun") +taraf = [n for n in source.body if isinstance(n, ast.FunctionDef) and n.name in ("ek_model_mu", "yonlendirilen")] class AccessPrograms(unittest.TestCase): @@ -34,7 +36,7 @@ class AccessPrograms(unittest.TestCase): "log": lambda message: None, "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "arac_semalarini_duzelt": lambda req: 0, "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [], "DEBUG": False, "UA": "test", "RETRIES": 1, "UPSTREAM_IDLE_TIMEOUT": 3900, } - exec(compile(ast.Module(body=[flatten, claude_sonu, responses], type_ignores=[]), "bridge.py", "exec"), self.env) + exec(compile(ast.Module(body=taraf + [flatten, claude_sonu, responses, ozgun], type_ignores=[]), "bridge.py", "exec"), self.env) def request(self, model): return {"model": model, "access_programs": {"cyber": "standard"}, @@ -74,8 +76,10 @@ class AccessPrograms(unittest.TestCase): def test_chatgpt_passthrough_preserves_original_body(self): body = json.dumps(self.request("gpt-6-astra")).encode() fake = Mock(headers={}) + fake._ozgun = lambda *a: self.env["_ozgun"](fake, *a) self.env["_responses"](fake, 1, "/responses", body) self.assertEqual(fake._passthrough.call_args.args[2], body) + self.assertFalse(fake._passthrough.call_args.kwargs["rewritten"]) self.upstream.assert_not_called() diff --git a/apps/desktop/assets/codex-bridge/test_combo_tekrar.py b/apps/desktop/assets/codex-bridge/test_combo_tekrar.py index 311f9b2..ab8c9d5 100644 --- a/apps/desktop/assets/codex-bridge/test_combo_tekrar.py +++ b/apps/desktop/assets/codex-bridge/test_combo_tekrar.py @@ -42,7 +42,8 @@ class ComboNotResent(unittest.TestCase): "DEBUG": False, "UA": "test", "RETRIES": 6, "UPSTREAM_IDLE_TIMEOUT": 3600, "NO_RETRY_PATTERNS": [], "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "arac_semalarini_duzelt": lambda req: 0, "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [], } - exec(compile(ast.Module(body=[pick("flatten_request"), pick("claude_sonu_duzelt"), pick("is_timeout"), pick("kopma_mi"), responses], + exec(compile(ast.Module(body=[pick("ek_model_mu"), pick("yonlendirilen"), pick("flatten_request"), pick("claude_sonu_duzelt"), + pick("is_timeout"), pick("kopma_mi"), responses], type_ignores=[]), "bridge.py", "exec"), self.env) def send(self, model): diff --git a/apps/desktop/assets/codex-bridge/test_denetim_ek.py b/apps/desktop/assets/codex-bridge/test_denetim_ek.py index 08f991a..1475633 100644 --- a/apps/desktop/assets/codex-bridge/test_denetim_ek.py +++ b/apps/desktop/assets/codex-bridge/test_denetim_ek.py @@ -253,7 +253,9 @@ class Zincir(unittest.TestCase): def setUp(self): self.env = build(top("nokta_mesaji_mi", "NOKTA_ISARETLERI", "is_timeout", "kopma_mi", "flatten_request", - "claude_sonu_duzelt") + [method("_responses"), method("_model_komutu")], { + "claude_sonu_duzelt", "ek_model_mu", "yonlendirilen") + + [method("_responses"), method("_model_komutu"), method("_ozgun")], { + "gecit_modelleri": lambda: [], "json": json, "re": __import__("re"), "os": __import__("os"), "copy": copy, "http": __import__("http.client"), "time": types.SimpleNamespace(sleep=lambda s: None, time=time.time), "urllib": types.SimpleNamespace(request=types.SimpleNamespace(Request=urllib.request.Request, urlopen=Mock()), @@ -269,6 +271,7 @@ class Zincir(unittest.TestCase): }) Sahte._responses = self.env["_responses"] Sahte._model_komutu = self.env["_model_komutu"] + Sahte._ozgun = self.env["_ozgun"] def send(self, model, items, key="sohbet-1", path="/responses"): fake = Sahte() @@ -276,40 +279,67 @@ class Zincir(unittest.TestCase): fake._responses(1, path, body) return fake, body - def test_mid_turn_note_reaches_the_subscription_model_behind_hook_messages(self): - items = [user("işe başla"), CALL, OUT, user("modeli sol yap"), developer("k1"), developer("k2"), developer("k3")] - fake, body = self.send("gpt-6-astra", items) - fake._yerel_yanit.assert_not_called() + def test_a_request_for_codex_own_model_is_passed_through_untouched(self): + """User decision, 11 Oct 2026: Codex's own models are one side, the added models the other; the bridge + does not touch a request for Codex's own model (no command, dot, note, work line or compaction handling).""" + self.env["CFG"].update({"nokta_karsila": True, "model_bilgi": True, "yarim_cevap": True, + "ozet_kopru": {"enabled": True, "kapsam": "hepsi"}, + "aiturk_hat": {"enabled": True, "komut": "aiturk is"}}) + mid_turn = [user("işe başla"), CALL, OUT, user("modeli opus 5 5 yap"), developer("k1")] + for items in ([user("modeli opus 5 5 yap")], [user(".")], [user("seçili model hangisi")], mid_turn, + [user("uzun iş"), {"type": "compaction_trigger"}]): + with self.subTest(items=items): + fake = Sahte(headers={"originator": "Codex Desktop"}) + body = json.dumps({"model": "gpt-6-astra", "prompt_cache_key": "sohbet-1", "input": items}).encode() + fake._responses(1, "/responses", body) + fake._yerel_yanit.assert_not_called() + args, kwargs = fake._passthrough.call_args + self.assertEqual(args[2], body) + self.assertFalse(kwargs["rewritten"]) + self.assertEqual(self.env["YONLENDIRME"].veri, {}) + + def test_only_the_bridges_own_traces_are_cleaned_for_codex_own_model(self): + self.env["fix_tool_ids"] = lambda req: 2 # ids left behind by an added model earlier in the chat + fake, body = self.send("gpt-6-astra", [user("devam")]) args, kwargs = fake._passthrough.call_args self.assertTrue(kwargs["rewritten"]) - self.assertNotEqual(args[2], body) - sent = json.loads(args[2]) - self.assertIn("ad belirsizdi", sent["input"][3]["content"][0]["text"]) - self.assertEqual(sent["model"], "gpt-6-astra") + self.assertEqual(json.loads(args[2]), json.loads(body)) + fake._yerel_yanit.assert_not_called() - def test_idle_switch_says_how_to_return_and_routes_the_next_request(self): - fake, _ = self.send("gpt-6-sol", [user("modeli gpt 6 astra yap")]) - answer = fake._yerel_yanit.call_args.args[1] - self.assertIn("Sohbetin modeli artık GPT-6-Astra", answer) - self.assertIn('"modeli GPT-6-Sol yap"', answer) - final = {"type": "message", "role": "assistant", "phase": "final_answer", "content": "ok"} - fake, _ = self.send("gpt-6-sol", [user("modeli gpt 6 astra yap"), final, user("devam")]) - sent = json.loads(fake._passthrough.call_args.args[2]) - self.assertEqual(sent["model"], "gpt-6-astra") - self.assertIn("GPT-6-Astra olarak değiştirdi", sent["input"][0]["content"][0]["text"]) - back, _ = self.send("gpt-6-sol", [user("modeli gpt 6 astra yap"), final, user("modeli gpt 6 sol yap")]) - self.assertNotIn("dönmek için", back._yerel_yanit.call_args.args[1]) # already back on the picked model + def test_voice_switch_stays_among_the_added_models(self): + opus, flash = "ide/9router-cc-claude-opus-5-5", "ide/9router-ag-gemini-3.8-flash-low" + self.env["ROUTES"]["aiturk-ide"] = {"url": "https://example.invalid/v1/responses", "key": "k"} + fake = Sahte() + answer = fake._model_komutu(1, {"input": [user("modeli gemini flash low yap")]}, "sohbet-1", opus) + self.assertIn("Sohbetin modeli artık agy/gemini-3.8-flash-low", answer) + self.assertEqual(self.env["yonlendirilen"]("sohbet-1", opus), flash) + # Codex's own model is not a voice target, from an added model either. + answer = fake._model_komutu(1, {"input": [user("modeli gpt 6 astra yap")]}, "sohbet-1", opus) + self.assertIn("GPT-6-Astra Codex'in kendi modeli", answer) + self.assertIn("sesle geçilmiyor", answer) + self.assertIn('"."', answer) + self.assertEqual(self.env["yonlendirilen"]("sohbet-1", opus), flash) + # In the middle of a turn the running model is told to pass that on; the work is not cut. + req = {"input": [user("işe başla"), CALL, OUT, user("modeli gpt 6 astra yap")]} + self.assertIsNone(fake._model_komutu(1, req, "sohbet-1", opus)) + self.assertIn("sesle yapılamıyor", req["input"][3]["content"][0]["text"]) + # A record left from before the rule that points at Codex's own model is ignored. + self.env["YONLENDIRME"].koy("sohbet-2", opus, "gpt-6-astra") + self.assertIsNone(self.env["yonlendirilen"]("sohbet-2", opus)) def test_a_refused_switch_is_not_told_as_done_after_compaction(self): items = [user("modeli opus 5 5 yap"), {"type": "compaction_trigger"}] # the ide/ route is closed here fake, body = self.send("gpt-6-sol", items) self.assertEqual(fake._passthrough.call_args.args[2], body) - def test_lone_dot_answer_says_selected_not_ready(self): + def test_lone_dot_is_answered_for_an_added_model_and_left_alone_for_codex_own(self): self.env["CFG"]["nokta_karsila"] = True - fake, _ = self.send("gpt-6-sol", [user(".")]) - self.assertIn("GPT-6-Sol seçildi", fake._yerel_yanit.call_args.args[1]) + fake, _ = self.send("ide/9router-cc-claude-opus-5-5", [user(".")]) + self.assertIn("cc/opus-5-5 seçildi", fake._yerel_yanit.call_args.args[1]) self.assertNotIn("hazır", fake._yerel_yanit.call_args.args[1]) + fake, body = self.send("gpt-6-sol", [user(".")]) + fake._yerel_yanit.assert_not_called() + self.assertEqual(fake._passthrough.call_args.args[2], body) def test_old_router_name_without_a_route_is_refused_locally(self): fake, _ = self.send("9r/cc/claude-opus-5-5", [user("selam")])