A model picked on the phone only arrives with a typed message that starts a turn. The bridge now handles "modeli X yap" itself: per-chat routing, same-turn handover when a model is working, truthful answer to "which model", and a lone "." joining a running turn no longer cuts the work. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
107 lines
6.4 KiB
Python
107 lines
6.4 KiB
Python
"""Bridge 8: "modeli X yap" is handled by the bridge; a per-chat route survives until the user picks another model."""
|
||
import tempfile
|
||
import unittest
|
||
|
||
import model_gecis as g
|
||
|
||
MODELS = [("gpt-6-sol", "GPT-6-Sol"), ("gpt-6-astra", "GPT-6-Astra"), ("gpt-6.1-sol", "GPT-6.1-Sol"),
|
||
("ide/9router-ag-gemini-3.8-flash-low", "agy/gemini-3.8-flash-low"),
|
||
("ide/9router-ag-gemini-3.8-flash-high", "agy/gemini-3.8-flash-high"),
|
||
("ide/9router-cc-claude-opus-5-5", "cc/opus-5-5"), ("ide/9router-cc-claude-opus-5-5-nt", "cc/opus-5-5 (no-think)"),
|
||
("ide/9router-nano-z-ai-glm-5.3-flash-uncensored", "nano/glm-5.3-flash-uncensored"),
|
||
("ide/agnes-agnes-2.0-flash", "agnes/agnes-2.0-flash"), ("ide/agnes-agnes-2.5-flash", "agnes/agnes-2.5-flash")]
|
||
|
||
|
||
def user(text):
|
||
return {"type": "message", "role": "user", "content": [{"type": "input_text", "text": text}]}
|
||
|
||
|
||
def voice(text):
|
||
return user(f"<realtime_delegation>\n <input>{text}</input>\n <transcript_delta>user: x</transcript_delta>\n</realtime_delegation>")
|
||
|
||
|
||
def pick(text):
|
||
command = g.komut_coz(text)
|
||
return g.eslestir(command["ad"], MODELS) if command and command["tur"] == "gec" else command
|
||
|
||
|
||
class Komut(unittest.TestCase):
|
||
def test_spoken_names_resolve_to_one_model(self):
|
||
for text, slug in [("modeli gemini flash low yap", "ide/9router-ag-gemini-3.8-flash-low"),
|
||
("Modeli Gemini 3.8 Flash High'a geçir", "ide/9router-ag-gemini-3.8-flash-high"),
|
||
("modeli klod opus yap", "ide/9router-cc-claude-opus-5-5"),
|
||
("modeli opusa geç", "ide/9router-cc-claude-opus-5-5"),
|
||
("modeli gpt 6 astra yap", "gpt-6-astra"), ("model geç gpt 6.1 sol", "gpt-6.1-sol"),
|
||
("modeli glm flaş sansürsüz yap", "ide/9router-nano-z-ai-glm-5.3-flash-uncensored")]:
|
||
self.assertEqual((pick(text) or {}).get("slug"), slug, text)
|
||
|
||
def test_ambiguous_name_lists_candidates_and_changes_nothing(self):
|
||
self.assertEqual(pick("modeli agnes yap"), {"adaylar": ["agnes/agnes-2.0-flash", "agnes/agnes-2.5-flash"]})
|
||
|
||
def test_ordinary_sentences_are_not_commands(self):
|
||
for text in ("Masaüstünde kaç klasör var", "bu modeli kullanarak bir site yap", "yapay zeka modeli nedir",
|
||
"modeli değiştir", "hangi modeli önerirsin bu iş için bana söyle bakalım lütfen",
|
||
"model " + "çok uzun bir cümle " * 5 + "yap"):
|
||
self.assertFalse(pick(text) and "slug" in pick(text), text)
|
||
self.assertEqual(g.komut_coz("seçili model hangisi"), {"tur": "sor"})
|
||
self.assertEqual(g.komut_coz("bu chatte ana model kim"), {"tur": "sor"})
|
||
|
||
def test_voice_delegation_yields_only_the_spoken_input(self):
|
||
text, index, spoken = g.son_kullanici_metni({"input": [user("a"), voice("modeli opus yap")]})
|
||
self.assertEqual((text, index, spoken), ("modeli opus yap", 1, True))
|
||
self.assertEqual(g.son_kullanici_metni({"input": [user("<skill>x</skill>")]})[0], None)
|
||
self.assertEqual(g.son_kullanici_metni({"input": [{"type": "function_call_output", "output": "x"}]})[0], None)
|
||
|
||
|
||
class TurOrtasi(unittest.TestCase):
|
||
def test_message_after_tool_items_is_mid_turn_and_after_final_answer_is_not(self):
|
||
final = {"type": "message", "role": "assistant", "phase": "final_answer", "content": "ok"}
|
||
call = {"type": "function_call", "call_id": "c", "name": "shell", "arguments": "{}"}
|
||
out = {"type": "function_call_output", "call_id": "c", "output": "x"}
|
||
self.assertFalse(g.tur_ortasi({"input": [user("a"), final, user(".")]}, 2))
|
||
self.assertTrue(g.tur_ortasi({"input": [user("a"), call, out, user(".")]}, 3))
|
||
self.assertTrue(g.tur_ortasi({"input": [user("a"), call, out, voice("bekliyorum"), user(".")]}, 4))
|
||
self.assertTrue(g.tur_ortasi({"input": [user("a"), {"type": "message", "role": "assistant", "phase": "commentary",
|
||
"content": "bakıyorum"}, user(".")]}, 2))
|
||
self.assertFalse(g.tur_ortasi({"input": [user(".")]}, 0))
|
||
|
||
def test_handover_note_replaces_only_the_spoken_input(self):
|
||
req = {"input": [voice("modeli opus yap")]}
|
||
g.metni_degistir(req, 0, g.devir_notu("cc/opus-5-5", "GPT-6-Sol"))
|
||
text = req["input"][0]["content"][0]["text"]
|
||
self.assertIn("<transcript_delta>", text)
|
||
self.assertIn("cc/opus-5-5 olarak devam ediyorum", text)
|
||
self.assertNotIn("modeli opus yap</input>", text)
|
||
|
||
|
||
class Gecmis(unittest.TestCase):
|
||
def test_handled_commands_in_history_become_notes_but_the_last_message_is_left_alone(self):
|
||
req = {"input": [user("modeli gemini flash low yap"), user("Masaüstünde kaç dosya var"),
|
||
voice("modeli opus yap"), user("modeli gpt 6 astra yap")]}
|
||
self.assertEqual(g.gecmisi_temizle(req, MODELS), 2)
|
||
self.assertIn("agy/gemini-3.8-flash-low olarak değiştirdi", req["input"][0]["content"][0]["text"])
|
||
self.assertEqual(req["input"][1]["content"][0]["text"], "Masaüstünde kaç dosya var")
|
||
self.assertIn("<transcript_delta>", req["input"][2]["content"][0]["text"])
|
||
self.assertEqual(req["input"][3]["content"][0]["text"], "modeli gpt 6 astra yap")
|
||
self.assertEqual(g.gecmisi_temizle(req, MODELS), 0)
|
||
|
||
|
||
class Yon(unittest.TestCase):
|
||
def test_route_lasts_until_the_user_picks_another_model_in_the_list(self):
|
||
with tempfile.TemporaryDirectory() as home:
|
||
y = g.Yonlendirme(home)
|
||
y.koy("chat", "gpt-6-sol", "ide/x", simdi=1000)
|
||
self.assertEqual(g.Yonlendirme(home).al("chat", "gpt-6-sol", simdi=1001), "ide/x") # survives a restart
|
||
self.assertIsNone(y.al("other", "gpt-6-sol", simdi=1001))
|
||
self.assertIsNone(y.al("chat", "gpt-6-astra", simdi=1002)) # user picked another
|
||
self.assertIsNone(y.al("chat", "gpt-6-sol", simdi=1003)) # and it stays cleared
|
||
y.koy("chat", "gpt-6-sol", "ide/x", simdi=1000)
|
||
y.koy("chat", "gpt-6-sol", "gpt-6-sol", simdi=1001) # back to the picked one
|
||
self.assertIsNone(y.al("chat", "gpt-6-sol", simdi=1002))
|
||
y.koy("old", "a", "b", simdi=1000)
|
||
self.assertIsNone(y.al("old", "a", simdi=1000 + g.OMUR_SN + 1))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|