fix(codex-link): stop list re-downloads and count context by measurement
Server CI and Windows package / windows-package (push) Successful in 12m27s
Server CI and Windows package / verify (push) Successful in 4m37s

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
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-20 23:40:43 +03:00
1 parent b347abe1f6
commit f1802299f0
9 files changed
+84 -10

No files matched your search

@@ -27,6 +27,16 @@ Run: `python bridge.py --home <data dir> --hermes-home <agent home>`. The IDE wr
Tests: `python -m unittest discover` in this directory.
## Version 26: list downloaded once, context counted by measurement (IDE beta.47)
- Model list: the upstream stamp (ETag) is kept. chatgpt.com repeats it in every answer (`x-models-etag`); the
bridge's own stamp made Codex re-download the ~17 MB merged list after every answer (6198 times on 20 October
2026). Added-model changes still arrive on Codex's own 5-minute list refresh.
- Token estimate: measured on 673 real requests (bridge bytes against provider-reported input tokens). Claude
family and unknown names use 2.5 bytes/token (Claude lowest quartile 2.55); recognised non-Claude families use
3.4 (their lowest 5%). The old flat 2 bytes/token counted non-Claude chats about twice as large, summarized too
early and raised false "Son mesaj ve zorunlu talimatlar hedefe sigmiyor" errors.
## Version 25: subscription WebSocket stays original (IDE beta.46)
User decision (20 October 2026): "Codex orjinal aynen kalacak, sadece ek combo ve model ekleyeceğiz."
+1 -1
View File
@@ -42,7 +42,7 @@ import gecis_context
import terminal_stream
HERE = os.path.dirname(os.path.abspath(__file__))
SURUM = "25" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
SURUM = "26" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
try:
with open(os.path.abspath(__file__), "rb") as _f:
KOD = hashlib.sha256(_f.read()).hexdigest()[:8]
@@ -68,9 +68,29 @@ 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):
# Deliberately more conservative than the old 3.2 bytes/token estimate.
return (len(encoded(value).encode("utf-8")) + 1) // 2
"""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):
+3 -3
View File
@@ -1628,7 +1628,6 @@ class H(http.server.BaseHTTPRequestHandler):
out = raw; changed = 0
raw_len = len(out)
duz = out # sikistirilmamis govde: yedege bu yazilir
catalog_etag = 'W/"astrabridge-' + hashlib.sha256(out).hexdigest()[:24] + '"'
enc_out = None
if "gzip" in (self.headers.get("Accept-Encoding") or "").lower() and changed:
import gzip
@@ -1637,8 +1636,9 @@ class H(http.server.BaseHTTPRequestHandler):
for k, v in resp.headers.items():
kl = k.lower()
if kl in RESP_HOP or kl in ("date", "set-cookie", "cf-ray"): continue
if kl in ("etag", "x-models-etag") and changed:
v = catalog_etag
# The upstream list stamp is kept (20 October 2026). chatgpt.com sends the same stamp in every answer
# (x-models-etag); a different stamp here made Codex re-download this ~17 MB list after every answer
# (6198 times in one day). Added-model changes still arrive on Codex's own 5-minute list refresh.
hdrs_out.append((k, v))
if changed:
# Yedek her zaman sikistirilmamis govdeyle tutulur (istemci gzip istese de).
@@ -483,6 +483,19 @@ class Liste(unittest.TestCase):
self.assertIn(("Content-Encoding", "gzip"), fake.sent)
env["save_last_models"].assert_called_once()
def test_the_list_keeps_the_upstream_stamp_so_codex_does_not_refetch_after_every_answer(self):
"""chatgpt.com repeats its list stamp (x-models-etag) in every answer; a changed stamp here made Codex
download the whole list again after each answer (6198 times on 20 October 2026)."""
env = self.env(EXTRA_MODELS=[{"slug": "ide/a", "display_name": "Aaa"}])
fake = Sahte({"chatgpt-account-id": "hesap"}, "GET")
resp = types.SimpleNamespace(read=lambda: json.dumps(self.UPSTREAM).encode(),
headers={"ETag": 'W/"40b107fc"', "x-models-etag": 'W/"40b107fc"'})
env["_models"](fake, resp, "[t]")
sent = dict(fake.sent)
self.assertEqual((sent["ETag"], sent["x-models-etag"]), ('W/"40b107fc"', 'W/"40b107fc"'))
self.assertIn("ide/a", [m["slug"] for m in json.loads(fake.wfile.getvalue())["models"]])
self.assertEqual(dict(env["LAST_MODELS"]["hesap"]["headers"])["ETag"], 'W/"40b107fc"')
def test_no_network_still_serves_the_last_list(self):
cached = {"headers": [("Content-Type", "application/json")], "body": b'{"models": []}', "at": "x"}
env = self.env(LAST_MODELS={"hesap": cached})
@@ -195,8 +195,9 @@ class PreparationService(unittest.TestCase):
def test_output_reserve_can_require_preparation_below_seventy_percent(self):
req = {"model": "ide/test", "max_output_tokens": 24000,
"input": [{"role": "user", "content": "x" * 20000}, {"role": "user", "content": "latest"}]}
"input": [{"role": "user", "content": "x" * 30000}, {"role": "user", "content": "latest"}]}
self.assertLess(gc.tokens(req), int(32768 * .7))
self.assertGreater(gc.tokens(req), 32768 - 24000)
result = self.service._prepare_transfer({"request": req})["request"]
self.assertLessEqual(gc.tokens(result), 32768 - 24000)
self.assertTrue(self.server.gelen)
@@ -446,5 +447,35 @@ class SocketMigration(fixtures.Ayirici):
self.assertEqual(len(self.kopru.gelen), 1)
class TokenEstimate(unittest.TestCase):
"""Measured bytes/token (20 October 2026): Claude ~2.55, other families >= 3.4. Unknown stays safe."""
def test_ratio_follows_the_measured_model_family_and_unknown_stays_safe(self):
for slug, ratio in (("ide/claude-claude-haiku-5-5", 2.5), ("ide/claude-claude-opus-5-5", 2.5),
("ide/nanogpt-Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled", 2.5),
("ide/Jail-Omni", 2.5), ("ide/Takim-Hizli", 2.5), ("", 2.5), (None, 2.5),
("ide/agy-gemini-3.8-flash-low", 3.4), ("gpt-6.1-sol", 3.4), ("ide/deepseek-deepseek-flash", 3.4),
("ide/abliteration-abliterated-model-large-v2", 3.4), ("ide/grok-cli-grok-4.7", 3.4)):
self.assertEqual(gc.bytes_per_token(slug), ratio, slug)
def test_estimate_never_counts_fewer_tokens_than_the_measured_lowest_ratio(self):
text = "Türkçe ve English karışık bir metin, kod: def f(x): return x * 2. " * 4000
size = len(gc.encoded({"model": "ide/claude-claude-opus-5-5", "input": text}).encode("utf-8"))
self.assertGreaterEqual(gc.tokens({"model": "ide/claude-claude-opus-5-5", "input": text}), size / 2.55)
size = len(gc.encoded({"model": "ide/agy-gemini-3.8-flash-low", "input": text}).encode("utf-8"))
self.assertGreaterEqual(gc.tokens({"model": "ide/agy-gemini-3.8-flash-low", "input": text}), size / 3.41)
self.assertGreater(gc.tokens([text]), gc.tokens({"model": "gpt-6.1-sol", "input": text})) # no model: safe
def test_a_chat_that_really_fits_a_non_claude_target_is_not_refused(self):
"""Before: 2 bytes/token counted this ~75k-token chat as ~130k and refused it for a 128k model."""
turn = [{"type": "message", "role": "user", "content": [{"type": "input_text", "text": "soru " + "x" * 64000}]},
{"type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "cevap " + "y" * 64000}]}]
req = {"model": "ide/agy-gemini-3.8-flash-low", "input": turn * 2}
self.assertLess(gc.tokens(req), int(128000 * 0.70))
self.assertGreater(len(gc.encoded(req).encode("utf-8")) // 2, int(128000 * 0.70)) # old estimate refused it
prepared = gc.Preparer().prepare(req, 128000, lambda text, budget: self.fail("must not summarize"))
self.assertEqual(prepared["input"], req["input"])
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -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 = '25'
export const CODEX_BRIDGE_VERSION = '26'
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'
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "aiturk-ide",
"productName": "AITURK IDE",
"private": true,
"version": "1.0.0-beta.46",
"version": "1.0.0-beta.47",
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
"author": "AITURK / TurkServis",
"repository": {
+1 -1
View File
@@ -65,7 +65,7 @@
},
"apps/desktop": {
"name": "aiturk-ide",
"version": "1.0.0-beta.46",
"version": "1.0.0-beta.47",
"license": "MIT",
"dependencies": {
"@assistant-ui/core": "0.2.23",