diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index 490b90a..81e4ab9 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -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}"}}) diff --git a/apps/desktop/assets/codex-bridge/ide_catalog.py b/apps/desktop/assets/codex-bridge/ide_catalog.py index 847c044..f062d46 100644 --- a/apps/desktop/assets/codex-bridge/ide_catalog.py +++ b/apps/desktop/assets/codex-bridge/ide_catalog.py @@ -201,6 +201,83 @@ def sirala_codex(modeller, ekler): modeller[:] = ozgunler + ek_modeller +# ---- Secicide model adinin altindaki satir ---- +# Telefonda bu satir tek satirdir ve yaklasik 30 karakterde kesilir (kullanici ekran goruntusu, +# 10 Ekim 2026); en ayirt edici bilgi basa yazilir, ad tekrar edilmez. +ACIKLAMA_SINIRI = 30 +_AILE = (("opus", "opus"), ("sonnet", "sonnet"), ("haiku", "haiku"), ("astra", "astra"), ("terra", "terra"), + ("luna", "luna"), ("sol", "sol"), ("gemini", "gemini"), ("qwen", "qwen"), ("glm", "glm"), + ("abliterated", "abliterated"), ("deepseek", "deepseek"), ("llama", "llama"), ("gemma", "gemma")) +_SAGLAYICI = {"cc": "Claude", "cx": "ChatGPT", "ag": "Antigravity", "nano": "NanoGPT", "hf": "HuggingFace", + "huggingface": "HuggingFace", "agnes": "Agnes", "bekci": "Bekçi"} +_SANSURSUZ = re.compile(r"uncensored|abliterat|obliterat|derestrict|heretic|unshackled", re.I) + + +def kisa_model(ad): + """"cc/claude-opus-5-5" -> "opus", "nano/z-ai/glm-5.3-flash-uncensored" -> "glm"; bilinmeyen ad "?".""" + if not ad: + return "?" + son = ad.split("/")[-1].split(":")[0].lower() + parcalar = [p for p in re.split(r"[^a-z]+", son) if p] + for anahtar, kisa in _AILE: + if anahtar in parcalar: + return kisa + return (parcalar[0] if parcalar else son) or "?" + + +def _yapraklar(dugum, derinlik=0): + """Sirali (priority) zinciri duz model listesine acar; ic takim kendi uyeleriyle yer alir.""" + if not isinstance(dugum, dict) or derinlik > 6: + return ["?"] + if dugum.get("kind") != "combo": + return [kisa_model(dugum.get("model"))] + cikti = [] + for uye in dugum.get("members") or []: + cikti.extend(_yapraklar(uye, derinlik + 1)) + return cikti or ["?"] + + +def _sigdir(onek, adlar, ayrac): + """Sinira sigdigi kadar ad yazar, kalani "+N" ile sayar.""" + tekil = [a for i, a in enumerate(adlar) if i == 0 or a != adlar[i - 1]] + for adet in range(len(tekil), 0, -1): + kalan = len(tekil) - adet + metin = onek + ayrac.join(tekil[:adet]) + (f" +{kalan}" if kalan else "") + if len(metin) <= ACIKLAMA_SINIRI or adet == 1: + return metin + return onek.rstrip(": ") + + +def aciklama(kimlik, ad, tur, kayit=None): + """Model adinin altinda gorunecek kisa satir. kayit: gecidin /router/catalog girdisi (yoksa None). + Takim -> uyeleri; sirali yedek -> sirasi; tek model -> saglayicisi ve adindan okunan ozellikleri.""" + if tur == "combo": + topoloji = (kayit or {}).get("topology") or {} + uyeler = topoloji.get("members") or [] + if topoloji.get("strategy") == "round-robin": + return f"Dönüşümlü: {len(uyeler)} üye" + takim = topoloji if topoloji.get("strategy") == "fusion" else None + if takim is None and uyeler and isinstance(uyeler[0], dict) and uyeler[0].get("strategy") == "fusion": + takim = uyeler[0] + if takim is not None: + return _sigdir("Takım: ", [kisa_model(u.get("model")) for u in takim.get("members") or []], "+") + if uyeler: + return _sigdir("Sıralı: ", _yapraklar(topoloji), ">") + return "Takım" + kisa = kisa_ad(ad) + if kisa.startswith(DOKUZ_ISARET): + kisa = kisa[len(DOKUZ_ISARET):] + kod = kisa.split("/")[0].lower() if "/" in kisa else (kimlik or "").split("-")[0].lower() + parcalar = [_SAGLAYICI.get(kod, kod) or "Tek model"] + if _SANSURSUZ.search(kisa) or _SANSURSUZ.search(kimlik or ""): + parcalar.append("sansürsüz") + if "thinking" in (kisa + " " + (kimlik or "")).lower(): + parcalar.append("düşünen") + if len(parcalar) == 1: + parcalar.insert(0, "Tek model") + return " · ".join(parcalar)[:ACIKLAMA_SINIRI] + + def yukle(ua="AstraBridge/1.0 (codex)", timeout=30): """Donus: (durum, base_url, anahtar, girdiler). durum: 'kapali' | 'anahtar-yok' | 'tamam'. Ag hatasinda istisna atar; cagiran son listeyi korur.""" @@ -227,4 +304,7 @@ def yukle(ua="AstraBridge/1.0 (codex)", timeout=30): girdiler = sec(data, adlar + [varsayilan], ayar["kapsam"], turler) if not girdiler: raise ValueError("Gecitte kullanilabilir OmniRoute sohbet modeli yok") + kayitlar = {m["id"]: m for m in metadata if m.get("id")} + for g in girdiler: + g["aciklama"] = aciklama(g["id"], g["ad"], g["kind"], kayitlar.get(g["id"])) return "tamam", base, anahtar, girdiler diff --git a/apps/desktop/assets/codex-bridge/ozet_kopru.py b/apps/desktop/assets/codex-bridge/ozet_kopru.py index 2cc4e6b..13a3756 100644 --- a/apps/desktop/assets/codex-bridge/ozet_kopru.py +++ b/apps/desktop/assets/codex-bridge/ozet_kopru.py @@ -45,6 +45,20 @@ OKUNAMADI_NOTU = ("(Kopru notu: Bu noktadan onceki konusma, onceki modelin sifre "aktarilamadi. Onceki kararlari bilmiyorsan uydurma; kullaniciya sor.)") +KISA_MESAJI = ("AstraBridge: Sohbet ozeti cok kisa cikti (%d karakter, en az %d bekleniyordu) ve gecmisin yerine " + "konmadi; mesajlariniz sohbette duruyor. Tekrar deneyin ya da daha guclu bir model secin.") + + +def en_az_karakter(girdi_bayt, ayar=None): + """Bu buyuklukteki bir konusmanin ozeti en az kac karakter olmali. + Olcu: 10 Ekim 2026'da 1.018.151 baytlik sohbet 2201 karaktere indi ve hafiza kayboldu. + Kucuk sohbetlerde esik kuculur ki kisa ama dogru ozetler reddedilmesin.""" + ayar = ayar or {} + tavan = int(ayar.get("en_az_tavan", 4000)) + oran = max(1, int(ayar.get("en_az_oran", 250))) # her 250 bayt girdi icin 1 karakter + return max(0, min(tavan, int(girdi_bayt) // oran)) + + def tetik_mi(req): """Istek bir sikistirma istegi mi (girdinin son ogesi compaction_trigger)?""" ogeler = req.get("input") @@ -129,9 +143,11 @@ class OzetAkisi: """Ozet isteginin SSE yanitini Codex'in bekledigi tek 'compaction' ogesine cevirir. ic: once calisan cevirici (SseRewriter). Metin gelmezse compaction ogesi uretmez (Codex hatayi gosterir).""" - def __init__(self, ic=None, tohum=""): + def __init__(self, ic=None, tohum="", en_az=0): self.ic = ic self.tohum = tohum + self.en_az = en_az # bundan kisa ozet kabul edilmez (bkz. en_az_karakter) + self.kisa = False self.buf = b"" self.parcalar = {} # item_id -> delta metni self.bitenler = [] # tamamlanan mesaj metinleri @@ -171,6 +187,13 @@ class OzetAkisi: self.bitti = True if not self.metin: return _olay(d) # ozet yok: Codex "0 compaction ogesi" hatasini gosterir + if len(self.metin) < self.en_az: + # Anormal kisa ozet gecmisin yerine konmaz: Codex hatayi gosterir, sohbet oldugu gibi kalir. + self.kisa = True + yanit = dict(d.get("response") or {}) + yanit["status"] = "failed"; yanit["output"] = [] + yanit["error"] = {"code": "astrabridge_ozet_kisa", "message": KISA_MESAJI % (len(self.metin), self.en_az)} + return _olay({"type": "response.failed", "response": yanit}) oge = ozet_ogesi(self.metin, self.tohum) yanit = dict(d.get("response") or {}) yanit["output"] = [oge] @@ -180,6 +203,14 @@ class OzetAkisi: return _olay(d) return b"" + def sonuc_notu(self): + """Gunluk satiri icin sonuc (icerik yazilmaz).""" + if not self.metin: + return "ozet URETILEMEDI" + if self.kisa: + return f"ozet REDDEDILDI: {len(self.metin)} karakter, en az {self.en_az} bekleniyordu; gecmis korunuyor" + return f"ozet uretildi, {len(self.metin)} karakter" + def feed(self, chunk): if self.ic: chunk = self.ic.feed(chunk) diff --git a/apps/desktop/assets/codex-bridge/test_member_catalog.py b/apps/desktop/assets/codex-bridge/test_member_catalog.py index 86763c0..9df8f56 100644 --- a/apps/desktop/assets/codex-bridge/test_member_catalog.py +++ b/apps/desktop/assets/codex-bridge/test_member_catalog.py @@ -76,6 +76,35 @@ class MemberCatalog(unittest.TestCase): self.assertEqual([m["priority"] for m in models[3:]], [44, 45, 46]) self.assertEqual([m["display_name"] for m in models[4:]], ["GPT-6-1-Sol (AITURK)", "GPT-6-Astra (AITURK)"]) + def test_description_line_tells_what_the_entry_is_and_fits_a_phone_row(self): + yedek = {"kind": "combo", "model": "Yedek", "strategy": "priority", "members": [ + {"kind": "model", "model": "nano/z-ai/glm-5.3-flash-uncensored"}, + {"kind": "model", "model": "nano/abliteration-ai/abliterated-model-large-v2"}]} + takim = {"topology": {"kind": "combo", "strategy": "priority", "members": [ + {"kind": "combo", "model": "Takim-Hizli", "strategy": "fusion", "members": [ + {"kind": "model", "model": "ag/gemini-3.8-flash-high"}, {"kind": "model", "model": "cc/claude-sonnet-5-5"}]}, + yedek]}} + self.assertEqual(c.aciklama("Takim-Hizli", "Takim-Hizli", "combo", takim), "Takım: gemini+sonnet") + sirali = {"topology": {"kind": "combo", "strategy": "priority", "members": [ + {"kind": "model", "model": "cx/gpt-6-astra"}, {"kind": "model", "model": "cc/claude-opus-5-5"}, + {"kind": "model", "model": "cc/claude-sonnet-5-5"}, {"kind": "model", "model": "ag/gemini-3.8-flash-high"}]}} + self.assertEqual(c.aciklama("Ajan-Arac", "Ajan-Arac", "combo", sirali), "Sıralı: astra>opus>sonnet +1") + # An unresolved first member stays visible instead of being hidden behind the fallback. + eksik = {"topology": {"kind": "combo", "strategy": "priority", "members": [{"kind": "combo", "model": None}, yedek]}} + self.assertEqual(c.aciklama("GPT-6-Sol", "GPT-6-Sol", "combo", eksik), "Sıralı: ?>glm>abliterated") + donen = {"topology": {"kind": "combo", "strategy": "round-robin", "members": [yedek, yedek, yedek]}} + self.assertEqual(c.aciklama("Takim-Sira", "Takim-Sira", "combo", donen), "Dönüşümlü: 3 üye") + self.assertEqual(c.aciklama("X", "X", "combo", None), "Takım") + self.assertEqual(c.aciklama("9router-cc-claude-opus-5-5", "9R · cc/claude-opus-5-5", "model"), "Tek model · Claude") + self.assertEqual(c.aciklama("9router-nano-qwen-qwen3.8-27b-uncensored-thinking", + "9R · nano/qwen3.8-27b-uncensored:thinking", "model"), + "NanoGPT · sansürsüz · düşünen") + self.assertEqual(c.aciklama("bekci-kurnaz", "kurnaz", "model"), "Tek model · Bekçi") + uzun = {"topology": {"kind": "combo", "strategy": "priority", "members": [ + {"kind": "model", "model": f"x/model{chr(97 + i) * 9}"} for i in range(8)]}} + self.assertLessEqual(len(c.aciklama("U", "U", "combo", uzun)), c.ACIKLAMA_SINIRI) + for ad in ("Takim-Hizli", "9R · cc/claude-opus-5-5"): + self.assertNotIn(ad, c.aciklama(ad, ad, "combo" if "Takim" in ad else "model", takim)) if __name__ == "__main__": unittest.main() diff --git a/apps/desktop/assets/codex-bridge/test_model_switch.py b/apps/desktop/assets/codex-bridge/test_model_switch.py index da8e035..db19603 100644 --- a/apps/desktop/assets/codex-bridge/test_model_switch.py +++ b/apps/desktop/assets/codex-bridge/test_model_switch.py @@ -62,6 +62,27 @@ class ModelSwitch(unittest.TestCase): self.assertEqual(rewritten["item"]["type"], "custom_tool_call") self.assertEqual(rewritten["item"]["input"], "remember-4827") self.assertEqual(rewritten["item"]["call_id"], "call_4827") + self.assertEqual(rewritten["item"]["id"], "ctc_chatcmpl-probe") + + def test_custom_stream_ids_are_valid_before_history_is_saved(self): + # Real failure after disabling the bridge and returning to OpenAI: + # input[32].id fc_call_... was saved as a custom_tool_call by Codex. + item = {"type": "function_call", "id": "fc_call_3f427ecc539d4c779c1252077701216f", + "call_id": "call_original", "name": "probe", + "arguments": json.dumps({"input": "keep this exact tool input"})} + adapter = env["SseRewriter"]({"probe"}) + converted = [] + for kind in ("response.output_item.added", "response.output_item.done"): + converted.append(adapter._fix({"type": kind, "item": dict(item)})["item"]) + final = adapter._fix({"type": "response.completed", "response": {"output": [dict(item)]}}) + converted.append(final["response"]["output"][0]) + for result in converted: + self.assertEqual(result["type"], "custom_tool_call") + self.assertEqual(result["id"], "ctc_call_3f427ecc539d4c779c1252077701216f") + self.assertEqual(result["call_id"], "call_original") + self.assertEqual(result["input"], "keep this exact tool input") + history = {"input": converted} + self.assertEqual(env["fix_tool_ids"](history), 0) if __name__ == "__main__": diff --git a/apps/desktop/assets/codex-bridge/test_ozet_kopru.py b/apps/desktop/assets/codex-bridge/test_ozet_kopru.py index 321c72a..1490e29 100644 --- a/apps/desktop/assets/codex-bridge/test_ozet_kopru.py +++ b/apps/desktop/assets/codex-bridge/test_ozet_kopru.py @@ -109,6 +109,31 @@ class Akis(unittest.TestCase): a = o.OzetAkisi() self.assertEqual(o.ozet_metni(coz(a.feed(ham))[1]["item"]), "ozet metni") + def test_anormal_kisa_ozet_gecmisin_yerine_konmaz(self): + # 10 Ekim 2026: 1.018.151 baytlik sohbet 2201 karaktere indi; bu ozet kabul edilmemeli. + self.assertEqual(o.en_az_karakter(1018151), 4000) + self.assertEqual(o.en_az_karakter(100000), 400) + self.assertEqual(o.en_az_karakter(0), 0) + self.assertEqual(o.en_az_karakter(1018151, {"en_az_tavan": 1500}), 1500) + ham = sse({"type": "response.created", "response": {"id": "r1"}}, + {"type": "response.output_item.done", "item": self._mesaj("k" * 2201)}, + {"type": "response.completed", "response": {"id": "r1", "status": "completed"}}) + a = o.OzetAkisi(tohum="7", en_az=o.en_az_karakter(1018151)) + olaylar = coz(a.feed(ham) + a.flush()) + self.assertEqual([e["type"] for e in olaylar], ["response.created", "response.failed"]) + self.assertNotIn("compaction", json.dumps(olaylar)) + self.assertEqual(olaylar[1]["response"]["error"]["code"], "astrabridge_ozet_kisa") + self.assertTrue(a.kisa) + self.assertIn("REDDEDILDI", a.sonuc_notu()) + # Yeterli uzunlukta ozet ayni esikle kabul edilir; kucuk sohbetin kisa ozeti de reddedilmez. + for metin, esik in (("u" * 4000, o.en_az_karakter(1018151)), ("kisa ama dogru ozet", o.en_az_karakter(3000))): + b = o.OzetAkisi(tohum="8", en_az=esik) + ham2 = sse({"type": "response.output_item.done", "item": self._mesaj(metin)}, + {"type": "response.completed", "response": {"id": "r2"}}) + cikti = coz(b.feed(ham2)) + self.assertEqual(o.ozet_metni(cikti[0]["item"]), metin) + self.assertFalse(b.kisa) + def test_metin_yoksa_compaction_uretilmez_hata_gorunur(self): ham = sse({"type": "response.created", "response": {}}, {"type": "response.output_item.done", "item": {"type": "function_call", "name": "x"}}, diff --git a/apps/desktop/electron/codex-link-config.ts b/apps/desktop/electron/codex-link-config.ts index 8586b37..dfdcb6b 100644 --- a/apps/desktop/electron/codex-link-config.ts +++ b/apps/desktop/electron/codex-link-config.ts @@ -27,7 +27,7 @@ export const CODEX_LINK_REFRESH_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama export const CODEX_LINK_MARKER = '# AITURK IDE Codex Baglama' // Must equal SURUM in assets/codex-bridge/bridge.py; a running bridge that reports // another version is restarted so an updated IDE never keeps an old bridge alive. -export const CODEX_BRIDGE_VERSION = '5' +export const CODEX_BRIDGE_VERSION = '6' export const CODEX_LINK_LINE = `openai_base_url = "${CODEX_LINK_ORIGIN}/backend-api/codex" ${CODEX_LINK_MARKER}` export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'