fix(codex-link): isolate model transfers and widen local connection capacity
Server CI and Windows package / verify (push) Successful in 2m3s
Server CI and Windows package / windows-package (push) Successful in 6m58s

This commit is contained in:
yilsem committed 2026-10-13 00:35:08 +03:00
1 parent 2718b460ed
commit 369f6c2ca8
16 files changed
+790 -40

No files matched your search

@@ -0,0 +1,163 @@
"""Provider-neutral history preparation. Never edits the saved Codex transcript.
Inference routing and voice are not responsibilities of this module. A caller
supplies the target window and an AITURK summarizer; subscription-only requests
must not enter the budget preparation path.
"""
import copy
import hashlib
import json
import threading
from collections import OrderedDict
import ozet_kopru
PREFIXES = {"custom_tool_call": "ctc", "custom_tool_call_output": "ctco",
"function_call": "fc", "function_call_output": "fc"}
RATIO = 0.70
class TransferError(ValueError):
pass
def encoded(value):
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def tokens(value):
# Deliberately more conservative than the old 3.2 bytes/token estimate.
return (len(encoded(value).encode("utf-8")) + 1) // 2
def input_budget(req, window):
return min(int(window * RATIO), window - max(1024, int(req.get("max_output_tokens") or 0)))
def foreign(req):
for item in req.get("input") or []:
if not isinstance(item, dict):
continue
identity = str(item.get("id") or "")
want = PREFIXES.get(item.get("type"))
if ("chatcmpl-" in identity or "astrabridge" in identity
or str(item.get("encrypted_content") or "").startswith(ozet_kopru.ISARET)
or (want and identity and not identity.startswith(want + "_"))):
return True
return False
def normalize(req):
"""Repair local wrappers/IDs, retaining real OpenAI encrypted compaction.
Only foreign server references (not inline tool results) are removed. Their
visible summary/text, when supplied, is retained as ordinary context.
"""
out = copy.deepcopy(req)
items = out.get("input")
if not isinstance(items, list):
return out
result = []
for item in items:
if not isinstance(item, dict):
result.append(item)
continue
encrypted = item.get("encrypted_content")
if item.get("type") == "compaction" and isinstance(encrypted, str) and encrypted.startswith(ozet_kopru.ISARET):
text = ozet_kopru.ozet_metni(item)
if not text:
raise TransferError("Eski AITURK özeti okunamadı; geçmiş değiştirilmedi.")
result.append(ozet_kopru._ozet_mesaji(text))
continue
identity = item.get("id")
if "chatcmpl-" in str(identity) and item.get("type") in ("reasoning", "item_reference"):
visible = [p.get("text", "") for p in item.get("summary", []) if isinstance(p, dict)]
if any(visible):
result.append(ozet_kopru._ozet_mesaji("\n".join(visible)))
continue
prefix = PREFIXES.get(item.get("type"))
if prefix and isinstance(identity, str) and identity and not identity.startswith(prefix + "_"):
item["id"] = prefix + "_" + identity.split("_", 1)[-1]
result.append(item)
out["input"] = result
return out
def split_history(items, keep_users=2):
"""Keep the last two user turns and complete tool pairs; never split a call.
System/developer items are kept verbatim regardless of their position.
"""
users = [i for i, x in enumerate(items) if isinstance(x, dict) and x.get("role") == "user"]
cut = users[-keep_users] if len(users) >= keep_users else (users[-1] if users else len(items))
# Some clients put a user/hook message between a call and its result.
while cut:
tail_calls = {x.get("call_id") for x in items[cut:] if isinstance(x, dict) and x.get("call_id")}
earlier = [i for i, x in enumerate(items[:cut]) if isinstance(x, dict) and x.get("call_id") in tail_calls]
if not earlier:
break
cut = min(earlier)
protected, old, recent = [], [], []
for i, item in enumerate(items):
if isinstance(item, dict) and item.get("role") in ("system", "developer"):
protected.append(item)
elif i < cut:
old.append(item)
else:
recent.append(item)
return protected, old, recent
class Preparer:
def __init__(self):
self.cache = OrderedDict()
self.lock = threading.Lock()
def prepare(self, request, window, summarize, scope="", force=False):
"""Summarize old history to fit 70%, leaving the original untouched.
summarize(text, max_tokens) must return a completed semantic summary or
raise. Tool execution is forbidden for that separate model request.
Cache keys include the caller scope, exact history and available budget.
"""
req = normalize(request)
window = int(window)
if window < 1024:
raise TransferError("Hedef modelin bağlam kapasitesi doğrulanamadı.")
limit = input_budget(req, window)
if not force and tokens(req) <= limit:
return req
protected, old, recent = split_history(req.get("input") or [])
base = dict(req, input=protected + recent)
available = limit - tokens(base) - 256
# Preserve the last user turn even if keeping two turns is too expensive.
if available < 1024 or not old:
protected, old, recent = split_history(req.get("input") or [], keep_users=1)
base = dict(req, input=protected + recent)
available = limit - tokens(base) - 256
if available < 512 or not old:
raise TransferError("Son mesaj ve zorunlu talimatlar hedefe sığmıyor; geçmiş korundu, modele eksik içerik gönderilmedi.")
# Encrypted items must be rendered by their owner, never summarized as ciphertext.
if any(isinstance(x, dict) and x.get("encrypted_content") for x in old):
raise TransferError("Şifreli geçmiş önce sahibi tarafından okunabilir özete çevrilmeli.")
source = encoded(old)
budget = min(12000, max(512, available // 2))
key = hashlib.sha256((scope + str(budget) + source).encode()).hexdigest()
with self.lock:
summary = self.cache.get(key)
if summary is None:
summary = summarize(source, budget)
if not isinstance(summary, str) or len(summary.strip()) < 80:
raise TransferError("AITURK özeti tamamlanamadı; eski geçmiş korundu.")
candidate = dict(req, input=protected + [ozet_kopru._ozet_mesaji(summary)] + recent)
if tokens(candidate) > limit:
raise TransferError("Hazırlanan özet hedef bütçesine sığmadı; eski geçmiş korundu.")
with self.lock:
self.cache[key] = summary
self.cache.move_to_end(key)
while len(self.cache) > 32:
self.cache.popitem(last=False)
return candidate
PREPARER = Preparer()