Files
aiturk-hermes-ide/apps/desktop/assets/codex-bridge/yarim.py
T
yilsemandClaude Opus 5.5 acf88f8314 feat(codex-link): bridge 7 - external models work like subscription models
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>
2026-10-10 22:04:58 +03:00

113 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)