"""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\n{_kisalt(metin)}\n") 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)