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:
1 parent
acf88f8314
commit
959f5f1459
7 files changed
+495
-15
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user