feat(codex-link): choose which models fill the Codex model menu and beta.41
Server CI and Windows package / verify (push) Successful in 2m54s
Server CI and Windows package / windows-package (push) Successful in 4m50s

The Codex desktop app's model picker asks the app server for one page of
at most 100 models and never asks for the next page (a fixed limit in the
app). AITURK sent 262, so everything after the first 100 in alphabetical
provider order was out of reach from the menu (Gemini, most NanoGPT
uncensored models, Grok, ModelArk...).

- Bridge: bridge.json codex_liste {medya_gizle, efor_gizle, one_al, gizli},
  re-read when the file changes. Image/audio/video models and "-low/-high"
  copies whose base model is listed are left out by default; pinned models
  follow Codex's own models in the given order; hidden ones are removed.
  The candidate list with each model's place is written to
  log/son-liste/codex-adaylar.json for the settings screen.
- Settings > Codex Bağlama: "Models shown in Codex" with a live count of the
  100 slots, search, filter, pin/move up/hide per model and the two switches.
- The IDE keeps codex_liste when it rewrites bridge.json.
- Bridge version 21, IDE 1.0.0-beta.41.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
yilsemandClaude Opus 5.5 committed 2026-10-15 05:16:40 +03:00
1 parent 81ee2ca646
commit 5c76c24c9c
17 files changed
+696 -8

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ import gecis_context
import terminal_stream
HERE = os.path.dirname(os.path.abspath(__file__))
SURUM = "20" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
SURUM = "21" # 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]
@@ -192,7 +192,41 @@ def sec(data, tanimli=(), kapsam="ide", turler=None):
return [{"id": s["id"], "ad": s["ad"], "kind": s["kind"], "yol": s["yol"]} for s in secilen[:EN_COK_MODEL]]
def sirala_codex(modeller, ekler, ozgun_kimlikler=()):
# ---- Codex secicisinde gorunen liste (15 Ekim 2026) ----
# Codex masaustu uygulamasinin model secicisi listeyi tek sayfada, en cok 100 modelle ister (uygulamanin kendi kodunda
# sabit; sonraki sayfayi istemez). Kullanici o 100'e hangi modellerin girecegini secer (bridge.json "codex_liste"):
# "one_al" ozgun modellerin hemen arkasina verdigi sirayla alinir, "gizli" cikarilir; resim/ses/video modelleri ve
# ana modeli listede olan dusuk-orta-yuksek kopyalari istege bagli gizlenir (varsayilan: gizli).
CODEX_SECICI_SINIRI = 100
_MEDYA = re.compile(r"(?:^|[-_/. ])(?:tts|image|imagen|veo|video|lyria|embedding|transcribe|whisper|flux|kokoro|orpheus|"
r"chatterbox|seedance|seedream|nano-banana|aqa|robotics|computer-use|realtime|music|audio|speech)"
r"(?=$|[-_/. ])", re.I)
_EFOR = re.compile(r"-(?:none|minimal|low|medium|high|xhigh|max)$", re.I)
def liste_ayari(ham):
"""bridge.json "codex_liste" -> {medya_gizle, efor_gizle, one_al, gizli}; eksik ya da bozuk alan varsayilandir."""
a = ham if isinstance(ham, dict) else {}
def liste(anahtar):
deger = a.get(anahtar)
return [str(s) for s in deger if isinstance(s, str) and s.strip()][:2000] if isinstance(deger, list) else []
return {"medya_gizle": a.get("medya_gizle") is not False, "efor_gizle": a.get("efor_gizle") is not False,
"one_al": list(dict.fromkeys(liste("one_al"))), "gizli": liste("gizli")}
def medya_mi(slug, ad=""):
"""Codex sohbetinde calismayan resim, ses, video, gomme modeli mi (kimlikten ya da adindan)."""
return bool(_MEDYA.search(slug or "") or _MEDYA.search(ad or ""))
def efor_kopyasi_mi(slug, kimlikler):
""""...-high" gibi bir kopya ve ana modeli de listede mi; Codex'in kendi dusunme seviyesi secimi ana modelde var."""
taban = _EFOR.sub("", slug or "")
return taban != slug and taban in kimlikler
def sirala_codex(modeller, ekler, ozgun_kimlikler=(), ayar=None):
"""Ozgun abonelik modelleri oldugu gibi kalir: sirasi, priority degeri ve adi degismez,
listenin basinda durur. Ek modeller onlarin ARKASINA eklenir: combo A-Z, sonra
saglayici A-Z / model A-Z. Ozgun bir modelle ayni adi tasiyan ek model " (AITURK)"
@@ -227,6 +261,26 @@ def sirala_codex(modeller, ekler, ozgun_kimlikler=()):
return (1, saglayici.casefold(), model.casefold(), slug)
ek_modeller.sort(key=anahtar)
adaylar = None
if ayar is not None:
# Kullanicinin secimi: once kimin gizlenecegi, sonra one alinanlar (verdigi sirayla); gerisi yukaridaki sirada.
ayar = liste_ayari(ayar)
kimlikler = {m.get("slug", "") for m in ek_modeller}
one_sira = {s: i for i, s in enumerate(s for s in ayar["one_al"] if s in kimlikler)}
gizli = set(ayar["gizli"])
adaylar, kalan = [], []
for m in ek_modeller:
slug = m.get("slug", "")
medya, efor = medya_mi(slug, m.get("display_name")), efor_kopyasi_mi(slug, kimlikler)
one = slug in one_sira
neden = ("gizli" if slug in gizli else "medya" if medya and ayar["medya_gizle"] and not one
else "efor" if efor and ayar["efor_gizle"] and not one else "")
adaylar.append({"slug": slug, "ad": m.get("display_name") or slug, "kind": bilgiler.get(slug, {}).get("kind") or "model",
"medya": medya, "efor": efor, "neden": neden})
if not neden:
kalan.append(m)
kalan.sort(key=lambda m: (0, one_sira[m.get("slug", "")]) if m.get("slug", "") in one_sira else (1, 0))
ek_modeller = kalan
taban = max([m.get("priority") or 0 for m in ozgunler], default=-1) + 1
for n, model in enumerate(ek_modeller):
model["priority"] = taban + n
@@ -234,6 +288,14 @@ def sirala_codex(modeller, ekler, ozgun_kimlikler=()):
if yalin(ad) in ozgun_adlar:
model["display_name"] = f"{ad}{AITURK_EKI}"
modeller[:] = (ozgunler + ek_modeller)[:EN_COK_MODEL]
if adaylar is None:
return None
# Secicide gorunecek yer: ozgun modeller once sayilir; Codex ilk 100 girdiyi gosterir.
yer = {m.get("slug", ""): i for i, m in enumerate(modeller)}
for a in adaylar:
a["sira"] = yer.get(a["slug"]) if not a["neden"] else None
a["menude"] = a["sira"] is not None and a["sira"] < CODEX_SECICI_SINIRI
return {"sinir": CODEX_SECICI_SINIRI, "ozgun_sayisi": len(ozgunler), "adaylar": adaylar}
# ---- Secicide model adinin altindaki satir ----
+37 -1
View File
@@ -442,6 +442,42 @@ def hat_ayari():
except Exception:
return CFG.get("aiturk_hat")
_LISTE_AYAR = {"mtime": None, "deger": None}
def liste_ayari():
"""bridge.json "codex_liste": Codex secicisinde gorunecek modeller (ayarlar ekrani yazar, yeniden baslatmadan uygulanir)."""
yol = os.path.join(HOME, "bridge.json")
try:
mt = os.path.getmtime(yol)
if mt != _LISTE_AYAR["mtime"]:
with open(yol, encoding="utf-8") as f:
_LISTE_AYAR["deger"] = (json.load(f) or {}).get("codex_liste")
_LISTE_AYAR["mtime"] = mt
return _LISTE_AYAR["deger"] or {}
except Exception:
return CFG.get("codex_liste") or {}
_ADAY_IZ = {"iz": None}
def aday_kaydet(sonuc):
"""Ayarlar ekrani icin aday listesi (log/son-liste/codex-adaylar.json); icerik degismediyse yazilmaz."""
if not sonuc:
return
try:
govde = json.dumps(sonuc, ensure_ascii=False, sort_keys=True)
iz = hashlib.sha256(govde.encode("utf-8")).hexdigest()
if iz == _ADAY_IZ["iz"]:
return
os.makedirs(CACHE_DIR, exist_ok=True)
yol = os.path.join(CACHE_DIR, "codex-adaylar.json")
gecici = "%s.%d.tmp" % (yol, threading.get_ident())
with open(gecici, "w", encoding="utf-8") as f:
json.dump({**sonuc, "at": time.strftime("%Y-%m-%d %H:%M:%S")}, f, ensure_ascii=False)
os.replace(gecici, yol)
_ADAY_IZ["iz"] = iz
except Exception as e:
log(f"Codex aday listesi yazilamadi: {e}")
LAST_MODELS = {} # hesap kimligi -> son basarili model listesi (chatgpt.com hata verirse bu verilir)
CACHE_DIR = os.path.join(LOGDIR, "son-liste")
@@ -1569,7 +1605,7 @@ class H(http.server.BaseHTTPRequestHandler):
m.update(saved)
models.append(m); changed += 1
if ekler:
ide_catalog.sirala_codex(models, ekler, ozgun_kimlikler)
aday_kaydet(ide_catalog.sirala_codex(models, ekler, ozgun_kimlikler, liste_ayari()))
out = json.dumps(data, ensure_ascii=False).encode("utf-8")
except Exception as e:
log(f"{tag} model listesi degistirilemedi: {e}")
@@ -0,0 +1,83 @@
"""Codex secicisinde gorunen liste: kullanicinin one aldigi, gizledigi modeller ve 100'luk secici siniri."""
import unittest
import ide_catalog as c
def liste(*slugs, ozgun=("gpt-6-sol", "gpt-6-astra")):
models = [{"slug": s, "display_name": s, "priority": i} for i, s in enumerate(ozgun)]
models += [{"slug": "ide/" + s, "display_name": s, "priority": 0} for s in slugs]
extras = [{"slug": "ide/" + s, "kind": "combo" if s.startswith("Takim") else "model"} for s in slugs]
return models, extras
class CodexListe(unittest.TestCase):
def test_no_setting_keeps_the_old_behaviour(self):
models, extras = liste("agnes-agnes-2.0-flash", "agnes-agnes-2.0-flash-high", "gemini-gemini-2.5-flash-preview-tts")
self.assertIsNone(c.sirala_codex(models, extras))
self.assertEqual(len(models), 5)
def test_default_hides_media_and_effort_copies_whose_base_is_listed(self):
models, extras = liste("agnes-agnes-2.0-flash", "agnes-agnes-2.0-flash-high", "agy-gemini-3.8-flash-high",
"gemini-gemini-2.5-flash-preview-tts", "gemini-gemini-3.1-flash-image", "gemini-aqa",
"claude-claude-sonnet-5-5")
sonuc = c.sirala_codex(models, extras, (), {})
slugs = [m["slug"] for m in models]
self.assertNotIn("ide/agnes-agnes-2.0-flash-high", slugs) # ana model listede: kopya gizlenir
self.assertIn("ide/agy-gemini-3.8-flash-high", slugs) # ana modeli yok: kalir
self.assertFalse([s for s in slugs if "tts" in s or "image" in s or "aqa" in s])
nedenler = {a["slug"]: a["neden"] for a in sonuc["adaylar"]}
self.assertEqual(nedenler["ide/agnes-agnes-2.0-flash-high"], "efor")
self.assertEqual(nedenler["ide/gemini-aqa"], "medya")
self.assertEqual(nedenler["ide/claude-claude-sonnet-5-5"], "")
self.assertEqual(sonuc["ozgun_sayisi"], 2)
def test_switches_off_show_everything(self):
models, extras = liste("agnes-agnes-2.0-flash", "agnes-agnes-2.0-flash-high", "gemini-gemini-2.5-flash-preview-tts")
c.sirala_codex(models, extras, (), {"medya_gizle": False, "efor_gizle": False})
self.assertEqual(len(models), 5)
def test_pinned_models_follow_the_subscription_models_in_the_given_order(self):
models, extras = liste("agnes-a", "claude-claude-opus-5-5", "Takim-Hizli", "nanogpt-glm-uncensored", "zeta-x")
originals = [dict(m) for m in models if not m["slug"].startswith("ide/")]
sonuc = c.sirala_codex(models, extras, (), {"one_al": ["ide/nanogpt-glm-uncensored", "ide/claude-claude-opus-5-5", "ide/yok"]})
self.assertEqual([m["slug"] for m in models[:4]],
["gpt-6-sol", "gpt-6-astra", "ide/nanogpt-glm-uncensored", "ide/claude-claude-opus-5-5"])
self.assertEqual([m for m in models if not m["slug"].startswith("ide/")], originals)
self.assertEqual([m["priority"] for m in models[2:]], list(range(2, 2 + len(models) - 2)))
sira = {a["slug"]: a["sira"] for a in sonuc["adaylar"]}
self.assertEqual(sira["ide/nanogpt-glm-uncensored"], 2)
def test_hidden_models_leave_and_a_pin_overrides_the_automatic_filters(self):
models, extras = liste("agnes-a", "agnes-a-high", "gemini-veo-3", "claude-x")
c.sirala_codex(models, extras, (), {"gizli": ["ide/claude-x"], "one_al": ["ide/agnes-a-high", "ide/gemini-veo-3"]})
slugs = [m["slug"] for m in models]
self.assertNotIn("ide/claude-x", slugs)
self.assertEqual(slugs[2:4], ["ide/agnes-a-high", "ide/gemini-veo-3"])
def test_menu_limit_counts_the_subscription_models_first(self):
slugs = ["model-%03d" % i for i in range(120)]
models, extras = liste(*slugs)
sonuc = c.sirala_codex(models, extras, (), {"one_al": ["ide/model-119"]})
aday = {a["slug"]: a for a in sonuc["adaylar"]}
self.assertTrue(aday["ide/model-119"]["menude"]) # one alindi: 3. sirada
self.assertEqual(aday["ide/model-119"]["sira"], 2)
menude = [a for a in sonuc["adaylar"] if a["menude"]]
self.assertEqual(len(menude), 100 - 2) # 2 ozgun + 98 ek model = 100
self.assertFalse(aday["ide/model-118"]["menude"])
def test_broken_setting_falls_back_to_defaults(self):
self.assertEqual(c.liste_ayari("bozuk"), {"medya_gizle": True, "efor_gizle": True, "one_al": [], "gizli": []})
self.assertEqual(c.liste_ayari({"one_al": ["a", "a", 3, " "], "gizli": "x"})["one_al"], ["a"])
def test_media_detection_does_not_catch_chat_models(self):
for sohbet in ("ide/claude-claude-opus-5-5", "ide/agnes-agnes-2.5-flash", "ide/gemini-gemini-3.8-flash",
"ide/nanogpt-z-ai-glm-5.3-flash-uncensored", "ide/bekci-sesli-bekci", "ide/Chat-Model"):
self.assertFalse(c.medya_mi(sohbet), sohbet)
for medya in ("ide/agnes-agnes-video-2.5", "ide/gemini-lyria-3-clip-preview", "ide/gemini-gemini-embedding-001",
"ide/gemini-gemini-3.5-transcribe", "ide/gemini-nano-banana-pro-preview", "ide/agnes-agnes-image-2.5-flash"):
self.assertTrue(c.medya_mi(medya), medya)
if __name__ == "__main__":
unittest.main()
@@ -457,6 +457,7 @@ class Liste(unittest.TestCase):
env = {"WATCH_OWNER": None, "WatchResponse": lambda response, accept: response, "json": json, "copy": copy, "hashlib": hashlib, "time": time, "log": lambda m: None, "CFG": {},
"decompress": lambda raw, enc: raw, "EXTRA_MODELS": [], "IDE_PREFIX": "ide/", "SLIM_CLONES": False,
"SLIM_FIELDS": [], "ide_catalog": ide_catalog, "LAST_MODELS": {}, "save_last_models": Mock(),
"liste_ayari": lambda: {}, "aday_kaydet": Mock(),
"RESP_HOP": {"content-length", "content-encoding"}, "REQ_HOP": {"host"}, "CHATGPT": "https://example.invalid",
"UPSTREAM_IDLE_TIMEOUT": 1,
"urllib": types.SimpleNamespace(request=types.SimpleNamespace(Request=urllib.request.Request, urlopen=Mock()),