Measured with the unmodified Codex binary on 10 Oct 2026: - real context windows per model (NanoGPT list, local table, combo = its smallest member, capped at the summarizer's window) so Codex compacts by itself when switching to a smaller model; an overflow guard trims oversized old parts and reports what still does not fit - classic tools instead of code mode for external models: GLM 0/3 -> 3/3, Qwen 0/3 -> 2/3 on simple tool tasks, Claude/cx/Gemini unchanged - phase (commentary / final_answer) on external assistant messages - an answer cut by the client is carried to the next turn once - images stay for vision-capable targets - summary brake stops tight loops, not frequent compaction - permanent gateway errors and tool-less targets fail at once with a readable message instead of five reconnects - a lone "." (used to apply a model change in voice) is answered locally - the bridge listens within seconds; the IDE waits 45 s for it Release: AITURK IDE 1.0.0-beta.26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
5.0 KiB
Python
113 lines
5.0 KiB
Python
"""Yarida kesilen cevabi sonraki modele tasir (10 Ekim 2026).
|
||
|
||
Olcum: Codex kapanir ya da kullanici cevabi durdurursa o ana kadar yazilan metin sohbet gecmisine girmiyor;
|
||
sonraki tur (ayni ya da baska modelle) "onceki cevabi goremiyorum" diyor. Kopru akisi zaten goruyor:
|
||
ISTEMCI baglantiyi kapattiginda (yanit bitmeden) o ana kadarki metni bellekte tutar ve ayni sohbetin
|
||
sonraki isteginde, kullanicinin yeni mesajina kisa bir not olarak ekler. Bir kez kullanilir.
|
||
|
||
Bilerek yapilmayanlar:
|
||
- Ust kaynak kopmasinda (istemci hala bagli) kayit yok: Codex o istegi kendisi yeniden gonderir; not eklenirse
|
||
model bastan yazmak yerine ortadan devam eder ve cevabin basi kaybolur.
|
||
- Yarim metin asistan mesaji olarak eklenmez: Claude "asistan mesajiyla biten istek"i reddeder ve abonelik
|
||
yolunda kimliksiz asistan ogesi risklidir. Kullanici mesajina eklenen not her saglayicida gecerlidir.
|
||
- Diske yazilmaz; kopru yeniden baslarsa kayit gider.
|
||
"""
|
||
import json
|
||
import threading
|
||
import time
|
||
|
||
OMUR_SN = 6 * 3600
|
||
EN_COK_KAYIT = 50
|
||
EN_COK_KARAKTER = 12_000 # nota konan yarim metnin ust siniri (basi ve sonu korunur)
|
||
EN_AZ_KARAKTER = 40 # bundan kisa yarim metin tasinmaya degmez
|
||
NOT_BASI = "[Köprü notu: bir önceki cevabın yarıda kesildi ve sohbet geçmişine kaydedilmedi."
|
||
_KILIT = threading.Lock()
|
||
_KAYIT = {} # sohbet anahtari -> {"zaman", "metin", "model"}
|
||
|
||
|
||
class Izleyici:
|
||
"""Akistan gecen parcalardan asistan metnini toplar; yanitin bitip bitmedigini izler."""
|
||
|
||
def __init__(self, anahtar, model):
|
||
self.anahtar, self.model = anahtar, model
|
||
self.buf = b""
|
||
self.parcalar = []
|
||
self.bitti = False
|
||
|
||
def feed(self, chunk):
|
||
self.buf += chunk
|
||
while b"\n\n" in self.buf:
|
||
blok, self.buf = self.buf.split(b"\n\n", 1)
|
||
if b"response.output_text.delta" in blok:
|
||
for satir in blok.split(b"\n"):
|
||
if satir.startswith(b"data:"):
|
||
try:
|
||
d = json.loads(satir[5:])
|
||
except ValueError:
|
||
continue
|
||
if d.get("type") == "response.output_text.delta" and isinstance(d.get("delta"), str):
|
||
self.parcalar.append(d["delta"])
|
||
elif b"response.completed" in blok or b"response.failed" in blok or b"response.incomplete" in blok:
|
||
self.bitti = True
|
||
|
||
@property
|
||
def metin(self):
|
||
return "".join(self.parcalar)
|
||
|
||
|
||
def kaydet(izleyici, simdi=None):
|
||
"""Istemci kapatti ve yanit bitmemisti: yarim metni sakla. True: saklandi."""
|
||
metin = izleyici.metin.strip()
|
||
if not izleyici.anahtar or izleyici.bitti or len(metin) < EN_AZ_KARAKTER:
|
||
return False
|
||
simdi = time.time() if simdi is None else simdi
|
||
with _KILIT:
|
||
_KAYIT[izleyici.anahtar] = {"zaman": simdi, "metin": metin, "model": izleyici.model}
|
||
for eski in sorted(_KAYIT, key=lambda k: _KAYIT[k]["zaman"])[:max(0, len(_KAYIT) - EN_COK_KAYIT)]:
|
||
del _KAYIT[eski]
|
||
return True
|
||
|
||
|
||
def _kisalt(metin):
|
||
if len(metin) <= EN_COK_KARAKTER:
|
||
return metin
|
||
bas, son = EN_COK_KARAKTER // 4, EN_COK_KARAKTER - EN_COK_KARAKTER // 4
|
||
return metin[:bas] + f"\n[... ortadan {len(metin) - bas - son} karakter çıkarıldı ...]\n" + metin[-son:]
|
||
|
||
|
||
def uygula(req, anahtar, simdi=None):
|
||
"""Ayni sohbetin sonraki isteginde yarim metni kullanicinin son mesajina not olarak ekler.
|
||
Donus: eklenen karakter sayisi (0: eklenmedi). Kayit yalniz basariyla eklenince ya da eskiyince silinir."""
|
||
if not anahtar:
|
||
return 0
|
||
simdi = time.time() if simdi is None else simdi
|
||
with _KILIT:
|
||
kayit = _KAYIT.get(anahtar)
|
||
if kayit and simdi - kayit["zaman"] > OMUR_SN:
|
||
del _KAYIT[anahtar]
|
||
kayit = None
|
||
girdi = req.get("input")
|
||
if not kayit or not isinstance(girdi, list) or not girdi:
|
||
return 0
|
||
son = girdi[-1]
|
||
if not (isinstance(son, dict) and son.get("type", "message") == "message" and son.get("role") == "user"):
|
||
return 0 # sikistirma istegi, arac sonucu turu vb.: yeni kullanici mesajini bekle
|
||
metin = kayit["metin"]
|
||
ham = json.dumps(girdi, ensure_ascii=False)
|
||
if json.dumps(metin[:200], ensure_ascii=False)[1:-1] in ham:
|
||
with _KILIT: # Codex bu metni gecmise kendisi yazmis: ikinci kez eklenmez
|
||
_KAYIT.pop(anahtar, None)
|
||
return 0
|
||
notu = (f"{NOT_BASI} Kesilmeden önce yazdığın metin aşağıda. Kullanıcı devam etmeni isterse baştan yazma, "
|
||
f"kaldığın yerden sürdür.]\n<yarim_cevap model=\"{kayit['model']}\">\n{_kisalt(metin)}\n</yarim_cevap>")
|
||
icerik = son.get("content")
|
||
if isinstance(icerik, str):
|
||
son["content"] = [{"type": "input_text", "text": icerik}, {"type": "input_text", "text": notu}]
|
||
elif isinstance(icerik, list):
|
||
icerik.append({"type": "input_text", "text": notu})
|
||
else:
|
||
return 0
|
||
with _KILIT:
|
||
_KAYIT.pop(anahtar, None)
|
||
return len(notu)
|