fix(codex-link): summarize with a subscription model, reject tiny summaries, describe added models

Model switch lost the conversation: on 10 Oct a 1 MB thread was compacted by the
newly selected small model into 2201 characters. The compaction summary for a bridge
model is now written by a subscription model (ozet_kopru.ozet_modeli, default
gpt-6.1-sol); if that request is refused before it starts, the target model is used
as before. A summary far shorter than the conversation warrants is not accepted:
Codex gets response.failed and keeps the history.

The line under each added model no longer repeats its name. Teams show their
members, fallback chains their order, single models their provider and the traits
readable from the name; the text fits one phone row (30 characters).

Also normalizes tool call ids in the streamed response so Codex persists ids the
subscription backend accepts (fc_ -> ctc_), and bumps the bridge protocol to 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-10 15:31:53 +03:00
1 parent ce49d6d1cc
commit adf73eeedd
7 files changed
+248 -13

No files matched your search

+60 -11
View File
@@ -26,7 +26,7 @@ from resim_context import resimleri_sadelestir
import ide_catalog
import ozet_kopru
SURUM = "5"
SURUM = "6"
HERE = os.path.dirname(os.path.abspath(__file__))
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
@@ -191,7 +191,8 @@ def load_ide_catalog():
if slug in model_map:
continue
extra_models.append({"slug": slug, "display_name": adlar[g["id"]],
"description": f"AITURK IDE: {g['ad']}", "route": IDE_ROUTE, "target": g["id"],
"description": g.get("aciklama") or f"AITURK IDE: {g['ad']}",
"route": IDE_ROUTE, "target": g["id"],
"clone_from": clone, "priority": 300 + n, "reasoning_levels": levels,
"kind": g["kind"]})
model_map[slug] = (IDE_ROUTE, g["id"], None)
@@ -426,6 +427,9 @@ class SseRewriter:
inp = args
d["item"] = {"type": "custom_tool_call", "id": item.get("id"), "call_id": item.get("call_id"),
"name": item.get("name"), "input": inp, "status": item.get("status", "completed")}
# Normalize before Codex persists the response. Request-only repair
# cannot help when the user later disables the bridge.
fix_tool_ids({"input": [d["item"]]})
return d
if t.startswith("response.function_call_arguments.") and d.get("item_id") in self.custom_ids:
return None # JSON parcalari custom input'a cevrilemez; done olayinda tam metin gidiyor
@@ -437,6 +441,7 @@ class SseRewriter:
except Exception: inp = item.get("arguments", "")
item = {"type": "custom_tool_call", "id": item.get("id"), "call_id": item.get("call_id"),
"name": item.get("name"), "input": inp, "status": "completed"}
fix_tool_ids({"input": [item]})
out.append(item)
d["response"]["output"] = out
return d
@@ -678,6 +683,40 @@ class H(http.server.BaseHTTPRequestHandler):
log(f"[{n} ozet kopru] OpenAI ozeti duz metne cevrildi ({len(metin)} karakter, onbellege yazildi)")
return metin or None
def _ozet_openai(self, n, path, req, slug, ozet_cfg, girdi_bayt):
"""Kopru modeline (ide/, 9r/) gelen sikistirma isteginin ozetini abonelik modeline yazdirir.
Donus: True = yanit istemciye gitti; False = abonelik istegi baslamadan basarisiz oldu
(istemciye hicbir sey yazilmadi, cagiran hedef modelle devam eder). req cagiranin kopyasidir."""
model = ozet_cfg.get("ozet_modeli", "gpt-6.1-sol")
tag = f"[{n} CHATGPT {model} <- {slug}]"
try:
req["model"] = model
fix_tool_ids(req)
atilan = drop_foreign_items(req)
ozet_kopru.ozetleri_ac(req, yabanci_hedef=False)
ozet_kopru.ozet_istegi_hazirla(req, ozet_cfg.get("istem"))
body = json.dumps(req, ensure_ascii=False).encode("utf-8")
except Exception as e:
log(f"{tag} ozet kopru: abonelik ozeti hazirlanamadi ({type(e).__name__}: {e}), hedef model deneniyor")
return False
hdrs = {k: v for k, v in self.headers.items() if k.lower() not in REQ_HOP and k.lower() != "content-encoding"}
hdrs["Content-Type"] = "application/json"; hdrs["Accept"] = "text/event-stream"
r = urllib.request.Request(CHATGPT + path, data=body, headers=hdrs, method="POST")
try:
resp = urllib.request.urlopen(r, timeout=UPSTREAM_IDLE_TIMEOUT)
except urllib.error.HTTPError as e:
log(f"{tag} ozet kopru: abonelik ozeti reddedildi (HTTP {e.code} {e.read()[:200]!r}), hedef model deneniyor")
return False
except Exception as e:
log(f"{tag} ozet kopru: abonelik ozeti baslatilamadi ({type(e).__name__}), hedef model deneniyor")
return False
log(f"{tag} ozet kopru: sikistirma ozeti abonelik modeline yazdiriliyor (girdi={len(req.get('input', []))} oge, "
f"{girdi_bayt} bayt, {atilan} yabanci oge atildi)")
akis = ozet_kopru.OzetAkisi(None, tohum=str(n), en_az=ozet_kopru.en_az_karakter(girdi_bayt, ozet_cfg))
self._stream(resp, resp.status, tag, akis)
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
return True
def _models(self, resp, tag):
raw = resp.read()
try:
@@ -797,8 +836,9 @@ class H(http.server.BaseHTTPRequestHandler):
log(f"[{n} CHATGPT {slug}] ozet kopru: DONGU FRENI, ozet istegi reddedildi")
return self._send_json(400, {"error": {"message": ozet_kopru.DONGU_MESAJI % OZET_FRENI.sinir,
"type": "invalid_request_error", "code": "astrabridge_ozet_dongusu"}})
girdi_bayt = len(json.dumps(req.get("input") or [], ensure_ascii=False).encode("utf-8"))
ozet_kopru.ozet_istegi_hazirla(req, ozet_cfg.get("istem"))
ozet_akisi = ozet_kopru.OzetAkisi(None, tohum=str(n))
ozet_akisi = ozet_kopru.OzetAkisi(None, tohum=str(n), en_az=ozet_kopru.en_az_karakter(girdi_bayt, ozet_cfg))
ob["acilan"] += 1
log(f"[{n} CHATGPT {slug}] ozet kopru: sikistirma istegi duz metin ozet istegine cevrildi (girdi={len(req.get('input', []))} oge)")
except Exception as e:
@@ -816,7 +856,7 @@ class H(http.server.BaseHTTPRequestHandler):
if parcalar: log(f"[{n} CHATGPT {slug}] " + ", ".join(parcalar))
if ozet_akisi is not None:
sonuc = self._passthrough(n, path, body, tagname=f"CHATGPT {slug}", rewritten=True, rewriter=ozet_akisi)
log(f"[{n} CHATGPT {slug}] ozet kopru: ozet {'uretildi, ' + str(len(ozet_akisi.metin)) + ' karakter' if ozet_akisi.metin else 'URETILEMEDI'}")
log(f"[{n} CHATGPT {slug}] ozet kopru: {ozet_akisi.sonuc_notu()}")
return sonuc
return self._passthrough(n, path, body, tagname=f"CHATGPT {slug}",
rewritten=bool(fixed or atilan or degisti))
@@ -826,11 +866,25 @@ class H(http.server.BaseHTTPRequestHandler):
except Exception as e:
ozet_tetik = False; ozet_acik = False
log(f"[{n} {slug}] ozet kopru atlandi: {type(e).__name__}: {e}")
girdi_bayt = 0
if ozet_tetik:
OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 3)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600))
if not OZET_FRENI.izin(str(req.get("prompt_cache_key") or slug), time.time()):
log(f"[{n} {slug}] ozet kopru: DONGU FRENI, ozet istegi reddedildi (ayni sohbette {OZET_FRENI.sinir} ozetten sonra)")
return self._send_json(400, {"error": {"message": ozet_kopru.DONGU_MESAJI % OZET_FRENI.sinir,
"type": "invalid_request_error", "code": "astrabridge_ozet_dongusu"}})
girdi_bayt = len(json.dumps(req.get("input") or [], ensure_ascii=False).encode("utf-8"))
if ozet_tetik and ozet_cfg.get("model"):
# Ozeti baglami alabilen baska bir modele yazdirmak icin (ornegin kucuk hedef kendi basina ozetleyemiyorsa).
alt = MODEL_MAP.get(ozet_cfg["model"])
if alt:
route_name, target, flat = alt
elif ozet_tetik and ozet_cfg.get("ozetleyen", "openai") == "openai":
# Ozeti secili kopru modeli degil, abonelikteki guclu model yazar (10 Ekim 2026: 1 MB'lik
# sohbeti kucuk bir model 2201 karaktere indirdi ve model degisince hafiza gitti).
# Abonelik istegi baslamadan reddedilirse asagidaki eski yol (hedef model) calisir.
if self._ozet_openai(n, path, copy.deepcopy(req), slug, ozet_cfg, girdi_bayt):
return
# 9Router cevirisinden gecen hedefler (9Router ve AITURK IDE gecidi): ayni istek duzeltmeleri uygulanir.
via_router = route_name in (catalog.get("route", "9router"), ide_route)
route = ROUTES[route_name]
@@ -844,11 +898,6 @@ class H(http.server.BaseHTTPRequestHandler):
if ob["acilan"] or ob["cozulen"] or ob["okunamayan"]:
log(f"{tag} ozet kopru: kopru ozeti={ob['acilan']}, OpenAI ozeti duz metne cevrildi={ob['cozulen']}, okunamayan={ob['okunamayan']}")
if ozet_tetik:
OZET_FRENI.sinir = int(ozet_cfg.get("dongu_siniri", 3)); OZET_FRENI.pencere = int(ozet_cfg.get("dongu_pencere_sn", 600))
if not OZET_FRENI.izin(str(req.get("prompt_cache_key") or slug), time.time()):
log(f"{tag} ozet kopru: DONGU FRENI, ozet istegi reddedildi (ayni sohbette {OZET_FRENI.sinir} ozetten sonra)")
return self._send_json(400, {"error": {"message": ozet_kopru.DONGU_MESAJI % OZET_FRENI.sinir,
"type": "invalid_request_error", "code": "astrabridge_ozet_dongusu"}})
ozet_kopru.ozet_istegi_hazirla(req, ozet_cfg.get("istem"))
log(f"{tag} ozet kopru: sikistirma istegi duz metin ozet istegine cevrildi (girdi={len(req.get('input', []))} oge)")
rewriter = None
@@ -919,9 +968,9 @@ class H(http.server.BaseHTTPRequestHandler):
time.sleep(min(2 * attempt, 8)); continue
# Headers go to the client from here on: a cut stream is never resent.
if ozet_tetik:
akis = ozet_kopru.OzetAkisi(rewriter, tohum=str(n))
akis = ozet_kopru.OzetAkisi(rewriter, tohum=str(n), en_az=ozet_kopru.en_az_karakter(girdi_bayt, ozet_cfg))
sonuc = self._stream(resp, resp.status, tag, akis)
log(f"{tag} ozet kopru: ozet {'uretildi, ' + str(len(akis.metin)) + ' karakter' if akis.metin else 'URETILEMEDI'}")
log(f"{tag} ozet kopru: {akis.sonuc_notu()}")
return sonuc
return self._stream(resp, resp.status, tag, rewriter)
self._send_json(503, {"error": {"message": f"bridge: hedef ulasilamadi {last}"}})