feat(codex-bridge): change the chat model by saying so; bridge 8, IDE 1.0.0-beta.27

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>
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-11 00:05:18 +03:00
1 parent acf88f8314
commit 959f5f1459
7 files changed
+495 -15

No files matched your search

+109 -12
View File
@@ -27,8 +27,9 @@ import ide_catalog
import ozet_kopru
import pencere
import yarim
import model_gecis
SURUM = "7"
SURUM = "8"
HERE = os.path.dirname(os.path.abspath(__file__))
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
@@ -583,7 +584,12 @@ def nokta_mesaji_mi(req):
girdi = req.get("input")
if not isinstance(girdi, list) or not girdi:
return False
son = girdi[-1]
son = None
for oge in reversed(girdi): # kancalarin kullanici mesajinin ardina ekledigi talimat mesajlari atlanir
if isinstance(oge, dict) and oge.get("type", "message") == "message" and oge.get("role") in ("developer", "system"):
continue
son = oge
break
if not (isinstance(son, dict) and son.get("type", "message") == "message" and son.get("role") == "user"):
return False
icerik = son.get("content")
@@ -651,6 +657,28 @@ def decompress(body, enc):
return body
OZET_FRENI = ozet_kopru.DonguFreni()
YONLENDIRME = model_gecis.Yonlendirme(HOME)
_BILINEN = {"kaynak": None, "liste": []}
def bilinen_modeller():
"""Codex'e en son verilen listedeki (slug, gorunen ad) ciftleri: abonelik modelleri + eklenenler."""
son = max(LAST_MODELS.values(), key=lambda v: str(v.get("at") or ""), default=None) if LAST_MODELS else None
govde = son.get("body") if son else None
if govde is not None and _BILINEN["kaynak"] is not govde:
try:
liste = [(m["slug"], m.get("display_name") or m["slug"]) for m in json.loads(govde).get("models", [])
if isinstance(m, dict) and m.get("slug")]
except (ValueError, AttributeError, TypeError):
liste = []
_BILINEN["kaynak"], _BILINEN["liste"] = govde, liste
liste = list(_BILINEN["liste"])
gorulen = {slug for slug, _ in liste}
liste += [(m["slug"], m.get("display_name") or m["slug"]) for m in EXTRA_MODELS
if m.get("slug") and m["slug"] not in gorulen]
return liste
def model_adi(slug):
return next((ad for kimlik, ad in bilinen_modeller() if kimlik == slug), None) or slug
class H(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
@@ -982,6 +1010,54 @@ class H(http.server.BaseHTTPRequestHandler):
self.wfile.write(out)
log(f"{tag} bitti status=200 {raw_len} bayt (gonderilen {len(out)}, {enc_out or 'sikistirmasiz'}), {changed} model girdisi degistirildi/eklendi")
def _yerel_yanit(self, slug, metin):
govde = hazir_yaniti(slug, metin)
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Content-Length", str(len(govde)))
self.end_headers()
self.wfile.write(govde)
def _model_komutu(self, n, req, sohbet, gelen):
"""Son kullanici mesaji "modeli X yap" / "hangi model" ise isler. Yerel cevap metni doner;
None: istek (gerekirse degistirilmis olarak) modele gider."""
metin, sira, sesli = model_gecis.son_kullanici_metni(req)
komut = model_gecis.komut_coz(metin) if metin else None
if not komut:
return None
etkin = YONLENDIRME.al(sohbet, gelen) or gelen
ortada = model_gecis.tur_ortasi(req, sira)
if komut["tur"] == "sor":
if ortada:
return None
log(f"[{n} {gelen}] model sorusu kopruden cevaplandi: {etkin}")
return f"Sohbetin modeli şu an {model_adi(etkin)}."
sonuc = model_gecis.eslestir(komut["ad"], bilinen_modeller())
if not sonuc:
return None # ad hicbir modele benzemiyor: komut degil, modele gider
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?")
log(f"[{n} {gelen}] model komutu belirsiz: {komut['ad']} -> {sonuc['adaylar']}")
if not ortada:
return soru
model_gecis.metni_degistir(req, sira, "[Köprü notu: kullanıcı model değiştirmek istedi ama ad belirsizdi; "
"model değişmedi. İşine devam et; cevabının sonunda şunu ilet: " + soru + "]")
return None
hedef = sonuc["slug"]
if hedef.startswith("ide/") and MODEL_MAP.get(hedef) is None and "aiturk-ide" not in ROUTES:
return "AITURK bağlantısı şu an kapalı; bu modele geçemedim, model değişmedi."
YONLENDIRME.koy(sohbet, gelen, hedef)
log(f"[{n} {gelen}] model komutu: {model_adi(etkin)} -> {sonuc['ad']}"
f" ({'tur ortasi, is devrediliyor' if ortada else 'bosta'}{', sesli' if sesli else ''})")
if hedef == etkin:
return None if ortada else f"Sohbetin modeli zaten {sonuc['ad']}."
if ortada:
model_gecis.metni_degistir(req, sira, model_gecis.devir_notu(sonuc["ad"], model_adi(etkin)))
return None
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.")
def _responses(self, n, path, body):
try:
req = json.loads(decompress(body, self.headers.get("Content-Encoding")))
@@ -989,21 +1065,42 @@ class H(http.server.BaseHTTPRequestHandler):
log(f"[{n} responses] govde cozulemedi: {e}")
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)
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 "")
if CFG.get("sesle_model", True) and sohbet:
try:
yerel = self._model_komutu(n, req, sohbet, gelen)
except Exception as e:
yerel = None
log(f"[{n} {gelen}] model komutu atlandi: {type(e).__name__}: {e}")
if yerel is not None:
return self._yerel_yanit(gelen, yerel)
try:
if model_gecis.gecmisi_temizle(req, bilinen_modeller()):
govde_degisti = True
except Exception as e:
log(f"[{n} {gelen}] gecmis komut temizligi atlandi: {type(e).__name__}: {e}")
hedef = YONLENDIRME.al(sohbet, gelen)
if hedef and hedef != gelen:
req["model"] = slug = hedef
log(f"[{n} {gelen}] sohbet yonlendirmesi: istek {hedef} modeline gidiyor")
mapping = MODEL_MAP.get(slug)
if CFG.get("nokta_karsila", True) and nokta_mesaji_mi(req) and model_gecis.tur_ortasi(req, model_gecis.son_sira(req["input"])):
# Calisan turun icine dusen nokta secimi tasimaz (olcum 10 Ekim 2026) ve cevaplanirsa isi yarida
# keser. Mesaj cikarilir, model isine devam eder; kullanici model bosalinca yeniden yazar.
del req["input"][model_gecis.son_sira(req["input"]):]
govde_degisti = True
log(f"[{n} {slug}] tur ortasinda gelen nokta cikarildi; calisan is kesilmedi")
if slug != gelen or once != (len(req.get("input") or []), json.dumps((req.get("input") or [None])[-3:], ensure_ascii=False, sort_keys=True)):
govde_degisti = True
if CFG.get("nokta_karsila", True) and nokta_mesaji_mi(req):
# Model degisimini uygulamak icin yazilan tek "." modele gonderilmez: hicbir model onu onay ya da
# istek sanip is yapmaz, yavas modelde asil soru beklemez. Secilen modelin adiyla aninda donulur.
ad = next((m.get("display_name") for m in EXTRA_MODELS if m.get("slug") == slug), None) or slug
govde = hazir_yaniti(slug, f"{ad} hazır.")
log(f"[{n} {slug}] nokta mesaji kopruden karsilandi (modele gonderilmedi)")
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Content-Length", str(len(govde)))
self.end_headers()
self.wfile.write(govde)
return
return self._yerel_yanit(gelen, f"{model_adi(slug)} hazır. Sohbetin modeli artık {model_adi(slug)}.")
# Ayni sohbetin onceki cevabi yarida kesildiyse (yarim.py) metni bu istege not olarak eklenir.
sohbet = str(req.get("prompt_cache_key") or "")
yarim_eklenen = 0
if CFG.get("yarim_cevap", True):
try:
@@ -1037,7 +1134,7 @@ class H(http.server.BaseHTTPRequestHandler):
# abonelik modeli: chatgpt.com'a oldugu gibi
fixed = fix_tool_ids(req)
atilan = drop_foreign_items(req)
degisti = bool(yarim_eklenen)
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