Files
aiturk-hermes-ide/apps/desktop/assets/codex-bridge/gecis_context.py
T
yilsemandClaude Opus 5.5 f1802299f0
Server CI and Windows package / windows-package (push) Successful in 12m27s
Server CI and Windows package / verify (push) Successful in 4m37s
fix(codex-link): stop list re-downloads and count context by measurement
Keep the upstream model-list ETag: chatgpt.com repeats it in every answer
(x-models-etag), and the bridge's own stamp made Codex re-download the
~17 MB merged list after every answer (6198 times in one day).

Replace the flat 2 bytes/token estimate with measured ratios (673 real
requests): Claude family and unknown names 2.5, recognised non-Claude
families 3.4. Non-Claude chats were counted about twice as large, were
summarized too early and got false "does not fit" errors.

Ship IDE beta.47 (bridge version 26).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XeNgWNzPjzUKQuS32RUYXx
2026-10-20 23:40:43 +03:00

328 lines
15 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.
"""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 re
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": "fco"}
RATIO = 0.70
PEER_OMITTED = "[Aktarım notu: Burada okunamayan şifreli bir alt ajan iletisi vardı; aktarılmadı.]"
REASONING_OMITTED = "[Aktarım notu: Şifreli model düşünme kaydı aktarılmadı.]"
def summary_missing(source, summary):
"""Check literal decisions, file paths and numeric settings, never secrets.
This is a loss detector for concrete references, not a semantic proof of
every sentence. Missing references must not enter a durable checkpoint.
"""
if isinstance(source, str):
try:
source = json.loads(source)
except ValueError:
pass
def strings(value):
if isinstance(value, str):
yield value
elif isinstance(value, dict):
for child in value.values():
yield from strings(child)
elif isinstance(value, list):
for child in value:
yield from strings(child)
text = '\n'.join(strings(source)).replace('\\', '/')
target = summary.replace('\\', '/').replace('`', '')
identifiers = set(re.findall(r'\b[A-Z][A-Z0-9]{0,23}(?:-[A-Z][A-Z0-9]{0,23}){0,3}-\d[A-Za-z0-9_-]{0,63}\b', text))
identifiers = {x for x in identifiers if not re.match(r'(?:SECRET|TOKEN|APIKEY|PASSWORD|OTP)-', x)}
paths = set(re.findall(r'(?<![A-Za-z0-9_.-])(?:[A-Za-z]:/(?:[A-Za-z0-9_.-]{1,160}/){0,24}|(?:[A-Za-z0-9_.-]{1,160}/){1,24})[A-Za-z0-9_.-]{1,160}\.(?:json|py|tsx?|jsx?|md|txt|ya?ml|toml|ini|sql|csv|log)\b', text)) if '/' in text else set()
assignments = set(re.findall(r'\b([A-Za-z_][A-Za-z0-9_]{0,63})\s*=\s*(\d{1,16}|true|false)\b', text))
assignments = {(key, value) for key, value in assignments
if not re.search(r'key|token|secret|password|sifre|anahtar|otp', key, re.I)}
missing = sum(value not in target for value in identifiers | paths)
missing += sum(not re.search(re.escape(key) + r'\s*[=:]\s*' + re.escape(value) + r'\b', target)
for key, value in assignments)
return missing
def require_summary_coverage(source, summary):
missing = summary_missing(source, summary)
if missing:
raise TransferError(f'Özet {missing} karar, dosya yolu veya sayısal ayarı korumadı; eksik özet kabul edilmedi.')
class TransferError(ValueError):
pass
def encoded(value):
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
# Bytes per token, measured on 673 real added-model requests (20 October 2026; bridge request bytes against the
# provider's reported input tokens): Claude family 2.55 at the lowest quartile (median 2.58); every other family
# 3.4 or more at the lowest 5% (Gemini, GPT, abliteration, Jail-Omni, bekci ~3.7-4.2). The old flat 2 bytes/token
# counted non-Claude chats about twice as large, summarized too early and raised false "does not fit" errors.
# Claude, profiles and anything unrecognised keep the safe 2.5; only a recognised non-Claude family gets 3.4.
SAFE_BYTES_PER_TOKEN = 2.5
NON_CLAUDE_BYTES_PER_TOKEN = 3.4
CLAUDE_HINTS = ("claude", "anthropic", "opus", "sonnet", "haiku")
NON_CLAUDE_HINTS = ("gemini", "gemma", "gpt", "grok", "deepseek", "qwen", "glm", "kimi", "llama", "mistral",
"nemotron", "minimax", "seed-", "mimo", "abliterat")
def bytes_per_token(model):
slug = str(model or "").lower()
if not slug or any(h in slug for h in CLAUDE_HINTS):
return SAFE_BYTES_PER_TOKEN
return NON_CLAUDE_BYTES_PER_TOKEN if any(h in slug for h in NON_CLAUDE_HINTS) else SAFE_BYTES_PER_TOKEN
def tokens(value):
"""Conservative token estimate. A request (dict with "model") uses that model's measured ratio."""
model = value.get("model") if isinstance(value, dict) else None
return int(len(encoded(value).encode("utf-8")) / bytes_per_token(model)) + 1
def input_budget(req, window, settings=None, automatic=False):
limit = int(window * RATIO)
if settings is not None:
limit = (int(window * settings['targetPercent'] / 100) if automatic else
settings['maxTokens'] if settings['budgetMode'] == 'tokens' else
int(window * settings['transferPercent'] / 100))
return min(limit, 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 native_opaque(req):
"""Native encrypted state belongs to the subscription, even in mixed history."""
return any(isinstance(item, dict) and (
(item.get("type") in ("compaction", "reasoning") and item.get("encrypted_content")
and not str(item["encrypted_content"]).startswith(ozet_kopru.ISARET))
or (item.get("type") == "agent_message" and any(
isinstance(part, dict) and part.get("type") == "encrypted_content"
for part in item.get("content") or [])))
for item in req.get("input") or [])
def foreign_history(req):
"""Semantic migration evidence, excluding tool ID format mismatches."""
return any(isinstance(item, dict) and (
"chatcmpl-" in str(item.get("id") or "")
or "astrabridge" in str(item.get("id") or "")
or str(item.get("encrypted_content") or "").startswith(ozet_kopru.ISARET))
for item in req.get("input") or [])
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
if ("chatcmpl-" in str(identity) or "astrabridge" in str(identity)) and (
item.get("type") == "message" or item.get("role")):
# Inline message content is portable; its foreign server ID is not.
item.pop("id", None)
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 portable_reasoning(req):
"""Project reasoning onto its visible text for a cross-provider request.
Encrypted reasoning is provider execution state, not a conversation
compaction. Never ask another model to interpret that ciphertext. Messages,
tool pairs and actual compaction items remain intact; callers must still
render opaque compaction with its owner. Native-only requests bypass this
projection, and the saved transcript is never modified.
"""
out = copy.deepcopy(req)
items = out.get("input")
if not isinstance(items, list):
return out
result = []
for item in items:
if isinstance(item, dict) and item.get("type") == "agent_message":
# Peer messages carry delegated tasks/results. External Responses
# translators do not understand this Codex-specific item type.
# Keep provenance as data, never as system/developer authority.
content = item.get("content") or []
if isinstance(content, str):
content = [{"type": "input_text", "text": content}]
portable = []
for part in content:
if isinstance(part, dict) and part.get("type") == "encrypted_content":
# Treat the typed wrapper as opaque regardless of whether
# its bytes happen to look like plaintext. Never decrypt,
# guess, or forward it to a different provider.
portable.append({"type": "input_text", "text": PEER_OMITTED})
else:
portable.append(part)
result.append({"type": "message", "role": "user", "content": [
{"type": "input_text", "text": "Agent message: " + encoded({
"author": item.get("author"), "recipient": item.get("recipient")})}
] + portable})
continue
if not isinstance(item, dict) or item.get("type") != "reasoning":
result.append(item)
continue
visible = []
for field in ("summary", "content"):
for part in item.get(field) or []:
if isinstance(part, dict) and isinstance(part.get("text"), str) and part["text"]:
visible.append(part["text"])
if item.get("encrypted_content"):
visible.append(REASONING_OMITTED)
if visible:
# Keep this on the assistant side: a synthetic user message would
# change split_history's count of protected user turns.
result.append({"type": "message", "role": "assistant",
"content": [{"type": "output_text", "text": "\n".join(visible)}]})
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[0] 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, settings=None,
automatic=False, checkpoints=None):
"""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 = portable_reasoning(normalize(request))
window = int(window)
if window < 1024:
raise TransferError("Hedef modelin bağlam kapasitesi doğrulanamadı.")
limit = input_budget(req, window, settings, automatic)
keep = settings['keepTurns'] if settings else 2
protected, old, recent = split_history(req.get("input") or [], keep)
original_old = old
saved = checkpoints.get(scope, old, settings) if checkpoints and old else None
if saved and summary_missing(old[:saved[0]], saved[1]):
saved = None
if saved:
old = [ozet_kopru._ozet_mesaji(saved[1])] + old[saved[0]:]
req = dict(req, input=protected + old + recent)
if not force and tokens(req) <= limit:
return req
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 settings is None and (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.")
# Actual compaction/unknown opaque items still require owner rendering.
# Reasoning execution state has already been projected to visible text.
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)
# The configured target already bounds the resulting context. Applying
# the level's ratio again denied the summarizer most of that budget.
# Level controls writing style; the whole available budget stays usable.
budget = max(512, available)
key = hashlib.sha256((scope + str(budget) + encoded(settings) + 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.")
require_summary_coverage(old, summary)
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.")
if checkpoints:
checkpoints.save(scope, original_old, settings, summary)
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()