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>
611 lines
28 KiB
Python
611 lines
28 KiB
Python
"""AITURK IDE "Codex Baglama" ayiricisi.
|
||
|
||
13 Ekim 2026 aciklamasi: abonelik ve ek modellerin saglayici yollari ayri
|
||
kalir; gecis gecmisinin hazirlanmasi yasak degildir. Ses Codex'in yerlisik
|
||
ses hattinda kalir. Saf abonelik sohbeti degistirilmez.
|
||
|
||
Codex'te tek adres ayari (`openai_base_url`) oldugu icin butun istekler buraya gelir. Bu dosya YALNIZ ayirir:
|
||
|
||
* Codex'in kendi modeli -> chatgpt.com'a; govde bayt bayt ayni gider, cevap geldigi gibi doner.
|
||
* Bizim ekledigimiz model -> ayri bir surecte calisan kopruye (kopru.py). Codex'in kendi modelinin istegi
|
||
(ide/..., 9r/..., model_map) o surece HIC gitmez.
|
||
* Model listesi -> kopru ayaktaysa ek modelleri eklenmis liste; degilse chatgpt.com'un listesi.
|
||
* Geri kalan her sey -> chatgpt.com'a, oldugu gibi.
|
||
|
||
Karisik gecmiste yerel aktarim hazirligi inference'dan ayridir. Abonelik
|
||
inference'i her zaman CHATGPT'ye; ek model inference'i KOPRU'ye gider.
|
||
Saf abonelik sohbetleri ve ses, ozet servisinin durumundan etkilenmez.
|
||
|
||
Veri klasoru (bridge.json, log/): --home <klasor> ya da AITURK_CODEX_BRIDGE_HOME. Ajan klasoru: --hermes-home.
|
||
Yerel denetim: GET /aiturk/codex-baglama/status, POST .../stop, POST .../refresh, POST .../kopru-yenile.
|
||
"""
|
||
import gzip
|
||
import hashlib
|
||
import http.client
|
||
import http.server
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
import threading
|
||
import time
|
||
import urllib.error
|
||
import urllib.request
|
||
import gecis_context
|
||
import terminal_stream
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
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]
|
||
except OSError:
|
||
KOD = ""
|
||
|
||
|
||
def _arg(name, env):
|
||
"""--name <deger> komut satirindan, yoksa ortam degiskeninden."""
|
||
if name in sys.argv[1:]:
|
||
k = sys.argv.index(name)
|
||
if k + 1 < len(sys.argv):
|
||
return sys.argv[k + 1]
|
||
return os.environ.get(env) or ""
|
||
|
||
|
||
HOME = _arg("--home", "AITURK_CODEX_BRIDGE_HOME") or HERE
|
||
HERMES = _arg("--hermes-home", "HERMES_HOME")
|
||
try:
|
||
with open(os.path.join(HOME, "bridge.json"), encoding="utf-8") as _f:
|
||
CFG = json.load(_f)
|
||
except (OSError, ValueError):
|
||
CFG = {}
|
||
PORT = int(CFG.get("port", 8790))
|
||
KOPRU_PORT = int(_arg("--kopru-port", "AITURK_CODEX_KOPRU_PORT") or PORT + 1)
|
||
CHATGPT = CFG.get("chatgpt_backend", "https://chatgpt.com").rstrip("/")
|
||
KOPRU = f"http://127.0.0.1:{KOPRU_PORT}"
|
||
BEKLEME = float(CFG.get("upstream_idle_timeout", 3600)) # okuma basina; uzun cevap kesilmez (28 Eylul 2026 karari)
|
||
LOGDIR = os.path.join(HOME, "log")
|
||
|
||
# Codex istek govdesini zstd ile sikistirir. Model adini okumak icin bir KOPYASI acilir; giden govde degismez.
|
||
_VENDOR = os.path.join(HERE, "vendor", "cp%d%d-win_amd64" % sys.version_info[:2])
|
||
if os.name == "nt" and os.path.isdir(_VENDOR):
|
||
sys.path.insert(0, _VENDOR)
|
||
|
||
|
||
def _zstd_ac(govde):
|
||
try:
|
||
from compression import zstd as _z
|
||
return _z.decompress(govde)
|
||
except ImportError:
|
||
import zstandard
|
||
return zstandard.ZstdDecompressor().decompressobj().decompress(govde)
|
||
|
||
|
||
def _zstd_hazir():
|
||
try:
|
||
try:
|
||
from compression import zstd as _z
|
||
ornek = _z.compress(b"aiturk")
|
||
except ImportError:
|
||
import zstandard
|
||
ornek = zstandard.ZstdCompressor().compress(b"aiturk")
|
||
return _zstd_ac(ornek) == b"aiturk"
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
# zstd yoksa model adi okunamaz ve ek modeller ayrilamaz; durum ucu ok=false der, IDE Codex'i buraya yoneltmez.
|
||
ZSTD_OK = _zstd_hazir()
|
||
|
||
kilit = threading.Lock()
|
||
log_kilit = threading.Lock()
|
||
transfer_kilit = threading.Lock()
|
||
sira = [0]
|
||
|
||
# Store only hashed conversation keys and the last external model, not messages
|
||
# or credentials. Survives restarts so returning long chats keep their guard.
|
||
TRANSFER_PATH = os.path.join(HOME, "gecis-sohbetler.json")
|
||
try:
|
||
with open(TRANSFER_PATH, encoding="utf-8") as f:
|
||
TRANSFERS = json.load(f)
|
||
if not isinstance(TRANSFERS, dict):
|
||
TRANSFERS = {}
|
||
except (OSError, ValueError):
|
||
TRANSFERS = {}
|
||
|
||
|
||
def remember_transfer(scope, model):
|
||
with transfer_kilit:
|
||
if TRANSFERS.get(scope) == model:
|
||
return
|
||
TRANSFERS[scope] = model
|
||
while len(TRANSFERS) > 2048:
|
||
del TRANSFERS[next(iter(TRANSFERS))]
|
||
try:
|
||
os.makedirs(HOME, exist_ok=True)
|
||
with open(TRANSFER_PATH + ".tmp", "w", encoding="utf-8") as f:
|
||
json.dump(TRANSFERS, f)
|
||
os.replace(TRANSFER_PATH + ".tmp", TRANSFER_PATH)
|
||
except OSError:
|
||
pass # in-memory guard remains active
|
||
|
||
|
||
def log(msg):
|
||
satir = time.strftime("%Y-%m-%d %H:%M:%S ") + msg
|
||
try:
|
||
os.makedirs(LOGDIR, exist_ok=True)
|
||
with log_kilit:
|
||
with open(os.path.join(LOGDIR, "ayirici.log"), "a", encoding="utf-8") as f:
|
||
f.write(satir + "\n")
|
||
except OSError:
|
||
pass
|
||
|
||
|
||
def ek_model_mu(slug):
|
||
"""Bizim ekledigimiz model mi? Degilse Codex'in kendi modelidir."""
|
||
slug = str(slug or "")
|
||
onek = (CFG.get("nine_router_catalog") or {}).get("prefix", "9r/")
|
||
return bool(slug) and (slug.startswith("ide/") or bool(onek and slug.startswith(onek))
|
||
or slug in (CFG.get("model_map") or {}))
|
||
|
||
|
||
def model_oku(govde, kodlama):
|
||
"""Istekteki model adi; okunamazsa None (o zaman istek Codex'in kendi yoluna gider). Govdeye dokunmaz."""
|
||
try:
|
||
kodlama = (kodlama or "").strip().lower()
|
||
ham = _zstd_ac(govde) if kodlama == "zstd" else gzip.decompress(govde) if kodlama == "gzip" else govde
|
||
veri = json.loads(ham)
|
||
return veri.get("model") if isinstance(veri, dict) else None
|
||
except Exception:
|
||
return None
|
||
|
||
|
||
ISTEK_ATLA = {"host", "content-length", "accept-encoding", "transfer-encoding", "connection"}
|
||
YANIT_ATLA = {"host", "content-length", "accept-encoding", "transfer-encoding", "connection", "content-encoding"}
|
||
|
||
|
||
def kopru_sor(yol, yontem="GET", sure=5.0, govde=None):
|
||
"""Kopru surecinin denetim ucuna sorar; (durum kodu, JSON) ya da ulasilamazsa (None, None)."""
|
||
try:
|
||
istek = urllib.request.Request(KOPRU + yol, data=govde, method=yontem)
|
||
with urllib.request.urlopen(istek, timeout=sure) as yanit:
|
||
return yanit.status, json.loads(yanit.read() or b"{}")
|
||
except Exception:
|
||
return None, None
|
||
|
||
|
||
class Kopru:
|
||
"""Bizim kopruyu (kopru.py) ayri surec olarak calistirir; duserse yeniden baslatir."""
|
||
|
||
def __init__(self):
|
||
self.surec = None
|
||
self.duruyor = False
|
||
self.baslangic = 0.0
|
||
|
||
def komut(self):
|
||
k = [sys.executable, os.path.join(HERE, "kopru.py"), "--home", HOME, "--port", str(KOPRU_PORT)]
|
||
return k + (["--hermes-home", HERMES] if HERMES else [])
|
||
|
||
def baslat(self):
|
||
self.baslangic = time.time()
|
||
self.surec = subprocess.Popen(self.komut(), cwd=HERE, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||
stderr=subprocess.DEVNULL,
|
||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
|
||
log(f"kopru baslatildi (surec {self.surec.pid}, kapi {KOPRU_PORT})")
|
||
|
||
def bekci(self):
|
||
"""Kopru duserse yeniden baslatir: hemen cokuyorsa araligi acar (2, 4, ... en cok 30 sn)."""
|
||
kopru_sor("/aiturk/codex-baglama/stop", "POST", 2.0, b"") # onceki ayiricidan kalmis sahipsiz kopru
|
||
time.sleep(0.5)
|
||
ara = 2.0
|
||
while not self.duruyor:
|
||
try:
|
||
self.baslat()
|
||
kod = self.surec.wait()
|
||
except Exception as e:
|
||
kod = f"{type(e).__name__}: {e}"
|
||
if self.duruyor:
|
||
break
|
||
omur = time.time() - self.baslangic
|
||
ara = 2.0 if omur > 60 else min(ara * 2, 30.0)
|
||
log(f"kopru durdu (cikis {kod}, {int(omur)} sn calisti); {int(ara)} sn sonra yeniden baslatilacak")
|
||
time.sleep(ara)
|
||
|
||
def yenile(self):
|
||
"""Yalniz kopruyu yeniden baslatir; Codex'in kendi modellerinin trafigi kesilmez."""
|
||
if kopru_sor("/aiturk/codex-baglama/stop", "POST", 3.0, b"")[0] is None and self.surec is not None:
|
||
try:
|
||
self.surec.terminate()
|
||
except Exception:
|
||
pass
|
||
|
||
def durdur(self):
|
||
self.duruyor = True
|
||
kopru_sor("/aiturk/codex-baglama/stop", "POST", 3.0, b"")
|
||
try:
|
||
if self.surec is not None:
|
||
self.surec.wait(timeout=3)
|
||
except Exception:
|
||
try:
|
||
self.surec.terminate()
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
KOPRU_SUREC = Kopru()
|
||
|
||
|
||
class H(http.server.BaseHTTPRequestHandler):
|
||
protocol_version = "HTTP/1.1"
|
||
|
||
def log_message(self, *a):
|
||
pass
|
||
|
||
def _oku(self):
|
||
if "chunked" in (self.headers.get("Transfer-Encoding") or "").lower():
|
||
out = bytearray()
|
||
while True:
|
||
ham = self.rfile.readline()
|
||
if not ham:
|
||
break
|
||
satir = ham.strip()
|
||
if not satir:
|
||
continue
|
||
boyut = int(satir.split(b";")[0], 16)
|
||
if boyut == 0:
|
||
while self.rfile.readline().strip():
|
||
pass
|
||
break
|
||
out += self.rfile.read(boyut)
|
||
self.rfile.readline()
|
||
return bytes(out)
|
||
n = int(self.headers.get("Content-Length") or 0)
|
||
return self.rfile.read(n) if n else b""
|
||
|
||
def _json(self, durum, veri):
|
||
b = json.dumps(veri).encode()
|
||
self.send_response(durum)
|
||
self.send_header("Content-Type", "application/json")
|
||
self.send_header("Content-Length", str(len(b)))
|
||
self.end_headers()
|
||
self.wfile.write(b)
|
||
|
||
def _denetim(self, yol, govde):
|
||
son = yol.split("?", 1)[0].rstrip("/")
|
||
if self.command == "POST" and son.endswith("/stop"):
|
||
self._json(200, {"ok": True})
|
||
log("AITURK IDE istegiyle kapatiliyor")
|
||
threading.Thread(target=lambda: (KOPRU_SUREC.durdur(), time.sleep(0.2), os._exit(0)), daemon=True).start()
|
||
return
|
||
if self.command == "POST" and son.endswith("/kopru-yenile"):
|
||
log("kopru yeniden baslatiliyor (istek uzerine); Codex'in kendi modelleri etkilenmez")
|
||
KOPRU_SUREC.yenile()
|
||
return self._json(200, {"ok": True})
|
||
if son.endswith("/status") or (self.command == "POST" and son.endswith("/refresh")):
|
||
kod, veri = kopru_sor(yol, self.command, 40.0 if son.endswith("/refresh") else 5.0,
|
||
govde if self.command == "POST" else None)
|
||
if kod == 200 and isinstance(veri, dict):
|
||
veri.update({"ayirici": True, "ayirici_kod": KOD, "kopru": "calisiyor"})
|
||
# IDE compares the splitter, not the older worker's version.
|
||
veri["surum"] = SURUM
|
||
veri["ok"] = bool(veri.get("ok")) and ZSTD_OK
|
||
return self._json(200, veri)
|
||
return self._json(200, {"ok": ZSTD_OK, "surum": SURUM, "kod": KOD, "zstd": ZSTD_OK, "ide_durum": "kapali",
|
||
"ide_model": 0, "ozet": False, "is_hatti": False, "ayirici": True,
|
||
"ayirici_kod": KOD, "kopru": "kapali"})
|
||
return self._json(404, {"error": {"message": "unknown control path"}})
|
||
|
||
def _ilet(self, n, hedef, yol, govde, etiket, yedekli=False, sessiz=False, rewritten=False, started=False):
|
||
"""Istegi hedefe OLDUGU GIBI iletir, cevabi geldigi gibi (akarak) geri verir.
|
||
yedekli=True: cevap basliklari gelmeden bir sorun olursa hicbir sey gonderilmez ve False doner (cagiran baska
|
||
hedefi dener). Diger durumlarda True doner."""
|
||
tag = f"[{n} {etiket} {self.command} {yol[:70]}]"
|
||
basliklar = {k: v for k, v in self.headers.items() if k.lower() not in ISTEK_ATLA}
|
||
if rewritten:
|
||
basliklar = {k: v for k, v in basliklar.items() if k.lower() != "content-encoding"}
|
||
if self.command == "GET" and "/models" in yol:
|
||
# Liste her seferinde tam gelsin: istemcinin elindeki onbellek damgasi iki tarafin listesini karistirir.
|
||
basliklar = {k: v for k, v in basliklar.items() if k.lower() not in ("if-none-match", "if-modified-since")}
|
||
istek = urllib.request.Request(hedef + yol, data=govde or None, headers=basliklar, method=self.command)
|
||
try:
|
||
yanit = urllib.request.urlopen(istek, timeout=20.0 if yedekli else BEKLEME)
|
||
durum = yanit.status
|
||
except urllib.error.HTTPError as e:
|
||
yanit, durum = e, e.code
|
||
except Exception as e:
|
||
if yedekli:
|
||
return False
|
||
log(f"{tag} HATA {type(e).__name__}: {e}")
|
||
if started:
|
||
self._transfer_fail("Model bağlantısı kurulamadı; sohbet geçmişi korundu.")
|
||
return True
|
||
if hedef == KOPRU:
|
||
self._json(503, {"error": {"message": "AITURK köprüsü şu an çalışmıyor; birkaç saniye sonra yeniden deneyin "
|
||
"ya da ekrandaki seçiciden Codex'in kendi modelini seçin."}})
|
||
else:
|
||
self._json(502, {"error": {"message": f"baglanti kurulamadi: {e}"}})
|
||
return True
|
||
if yedekli and durum != 200:
|
||
try:
|
||
yanit.read()
|
||
except Exception:
|
||
pass
|
||
return False
|
||
if durum >= 400:
|
||
hata = yanit.read()
|
||
log(f"{tag} {durum}: {hata[:300]!r}")
|
||
if started:
|
||
try:
|
||
message = json.loads(hata).get("error", {}).get("message", "Model isteği reddedildi.")
|
||
except (ValueError, AttributeError):
|
||
message = "Model isteği reddedildi (HTTP %d)." % durum
|
||
self._transfer_fail(message)
|
||
return True
|
||
self.send_response(durum)
|
||
for k, v in yanit.headers.items():
|
||
if k.lower() not in YANIT_ATLA:
|
||
self.send_header(k, v)
|
||
self.send_header("Content-Length", str(len(hata)))
|
||
self.end_headers()
|
||
self.wfile.write(hata)
|
||
return True
|
||
observer = None
|
||
first_chunks = []
|
||
failure_key = getattr(self, "_failure_key", None)
|
||
if (hedef == KOPRU and failure_key is not None and durum == 200
|
||
and "text/event-stream" in yanit.headers.get("Content-Type", "")
|
||
and yanit.headers.get("Content-Encoding", "identity") == "identity"):
|
||
observer = terminal_stream.Observer()
|
||
if not started:
|
||
# Inspect only until the first SSE frame/comment. Keep normal
|
||
# streaming and heartbeats; an error-only response can still
|
||
# return a truthful HTTP failure before headers are committed.
|
||
try:
|
||
while True:
|
||
first = yanit.read1(4096)
|
||
if not first:
|
||
break
|
||
first_chunks.append(first)
|
||
observer.feed(first)
|
||
if observer.error and not observer.payload_seen:
|
||
yanit.close()
|
||
self._json(400, terminal_stream.error_body(observer.error))
|
||
return True
|
||
if observer.comment_seen or observer.payload_seen or sum(map(len, first_chunks)) >= observer.LIMIT:
|
||
break
|
||
except (OSError, http.client.HTTPException):
|
||
# A transport interruption without an explicit provider
|
||
# failure keeps the client's existing recovery behavior.
|
||
yanit.close()
|
||
self.close_connection = True
|
||
return True
|
||
if not started:
|
||
self.send_response(durum)
|
||
for k, v in yanit.headers.items():
|
||
if k.lower() not in YANIT_ATLA:
|
||
self.send_header(k, v)
|
||
self.send_header("Transfer-Encoding", "chunked")
|
||
self.end_headers()
|
||
toplam = 0
|
||
event_tail = b""
|
||
completed = False
|
||
try:
|
||
for first in first_chunks:
|
||
toplam += len(first)
|
||
self.wfile.write(b"%x\r\n" % len(first) + first + b"\r\n")
|
||
self.wfile.flush()
|
||
while True:
|
||
try:
|
||
parca = yanit.read1(4096) # gelen kadarini hemen verir; bekletmez
|
||
except (OSError, http.client.HTTPException) as e:
|
||
# Hedef koptu: baglanti kapatilir ki Codex yarim akisi tam sanmasin.
|
||
log(f"{tag} hedef koptu ({toplam} bayt): {type(e).__name__}: {e}")
|
||
self.close_connection = True
|
||
return True
|
||
if not parca:
|
||
break
|
||
toplam += len(parca)
|
||
if observer is not None:
|
||
observer.feed(parca)
|
||
if getattr(self, "_native_return_scope", None):
|
||
event_tail += parca
|
||
while b"\n" in event_tail:
|
||
line, event_tail = event_tail.split(b"\n", 1)
|
||
if line.startswith(b"data:"):
|
||
try:
|
||
event = json.loads(line[5:])
|
||
completed = completed or event.get("type") == "response.completed"
|
||
except (ValueError, AttributeError):
|
||
pass
|
||
self.wfile.write(b"%x\r\n" % len(parca) + parca + b"\r\n")
|
||
self.wfile.flush()
|
||
if observer is not None and observer.error and not observer.completed:
|
||
self.server.failure_replays.remember(failure_key, observer.error)
|
||
log(f"{tag} terminal worker error; identical automatic replay will return HTTP 400")
|
||
self.wfile.write(b"0\r\n\r\n")
|
||
self.wfile.flush()
|
||
except (ConnectionAbortedError, BrokenPipeError, ConnectionResetError):
|
||
log(f"{tag} istemci baglantiyi kapatti ({toplam} bayt)")
|
||
self.close_connection = True
|
||
return True
|
||
if not sessiz:
|
||
log(f"{tag} bitti status={durum} {toplam} bayt")
|
||
if completed and getattr(self, "_native_return_scope", None):
|
||
remember_transfer(*self._native_return_scope)
|
||
return True
|
||
|
||
def _transfer_begin(self):
|
||
self.send_response(200)
|
||
self.send_header("Content-Type", "text/event-stream")
|
||
self.send_header("Transfer-Encoding", "chunked")
|
||
self.end_headers()
|
||
|
||
def _transfer_chunk(self, body):
|
||
self.wfile.write(b"%x\r\n" % len(body) + body + b"\r\n")
|
||
self.wfile.flush()
|
||
|
||
def _transfer_fail(self, message):
|
||
event = {"type": "response.failed", "response": {"id": "resp_aiturk_transfer", "status": "failed",
|
||
"output": [], "error": {"code": "aiturk_transfer_failed", "message": message}}}
|
||
self._transfer_chunk(b"event: response.failed\ndata: " + json.dumps(event, ensure_ascii=False).encode() + b"\n\n")
|
||
self.wfile.write(b"0\r\n\r\n")
|
||
self.wfile.flush()
|
||
|
||
def _prepare(self, req, source_model):
|
||
"""Preparation is separate from inference. ChatGPT auth never goes to AITURK upstream.
|
||
|
||
The local worker needs subscription auth only to render opaque OpenAI
|
||
summaries through their owner. It returns history, never a task answer.
|
||
"""
|
||
done, result = threading.Event(), {}
|
||
def run():
|
||
try:
|
||
headers = {"Content-Type": "application/json", "X-AITURK-Transfer": "1"}
|
||
for key in ("Authorization", "chatgpt-account-id", "originator"):
|
||
if self.headers.get(key):
|
||
headers[key] = self.headers[key]
|
||
data = json.dumps({"request": req, "source_model": source_model}, ensure_ascii=False).encode()
|
||
request = urllib.request.Request(KOPRU + "/aiturk/codex-baglama/prepare-transfer", data=data, headers=headers)
|
||
with urllib.request.urlopen(request, timeout=BEKLEME) as response:
|
||
result["value"] = json.load(response)
|
||
except urllib.error.HTTPError as e:
|
||
try:
|
||
result["error"] = json.loads(e.read())["error"]["message"]
|
||
except Exception:
|
||
result["error"] = "Geçiş hazırlığı başarısız; geçmiş korundu."
|
||
except Exception:
|
||
result["error"] = "AITURK geçiş hazırlığına ulaşılamadı; geçmiş korundu."
|
||
finally:
|
||
done.set()
|
||
threading.Thread(target=run, daemon=True).start()
|
||
started = not done.wait(0.2)
|
||
if started:
|
||
self._transfer_begin()
|
||
self._transfer_chunk(b": AITURK context preparation\n\n")
|
||
while not done.wait(10):
|
||
self._transfer_chunk(b": AITURK context preparation\n\n")
|
||
if "error" in result:
|
||
if not started:
|
||
self._transfer_begin()
|
||
self._transfer_fail(result["error"])
|
||
return None, True
|
||
return result["value"], started
|
||
|
||
def _isle(self):
|
||
self._native_return_scope = None
|
||
self._failure_key = None
|
||
with kilit:
|
||
sira[0] += 1
|
||
n = sira[0]
|
||
if "websocket" in (self.headers.get("Upgrade") or "").lower():
|
||
# Bugunku calisan durum: surekli acik baglanti reddedilir, Codex olagan yola duser.
|
||
self.send_response(426)
|
||
self.send_header("Content-Length", "0")
|
||
self.send_header("Connection", "close")
|
||
self.end_headers()
|
||
return
|
||
govde = self._oku()
|
||
yol = self.path
|
||
if yol.startswith("/aiturk/codex-baglama/"):
|
||
return self._denetim(yol, govde)
|
||
son = yol.split("?", 1)[0].rstrip("/")
|
||
if self.command == "POST" and son.endswith("/responses"):
|
||
model = model_oku(govde, self.headers.get("Content-Encoding"))
|
||
external = ek_model_mu(model)
|
||
try:
|
||
encoding = (self.headers.get("Content-Encoding") or "").lower()
|
||
raw = _zstd_ac(govde) if encoding == "zstd" else gzip.decompress(govde) if encoding == "gzip" else govde
|
||
req = json.loads(raw)
|
||
if not isinstance(req, dict):
|
||
req = {}
|
||
except Exception:
|
||
req = {}
|
||
if external:
|
||
self._failure_key = terminal_stream.request_key(req or {"raw": govde.hex()}, self.headers)
|
||
previous_failure = self.server.failure_replays.take(self._failure_key)
|
||
if previous_failure:
|
||
log(f"[{n} {model}] terminal worker error replay stopped before upstream request")
|
||
return self._json(400, terminal_stream.error_body(previous_failure))
|
||
thread = str(req.get("prompt_cache_key") or "")
|
||
scope = hashlib.sha256((str(self.headers.get("chatgpt-account-id") or "") + "|" + thread).encode()).hexdigest()
|
||
source = TRANSFERS.get(scope) if thread else None
|
||
# A completed return to the subscription ends migration. Historical
|
||
# foreign IDs must not keep later native turns in the preparer.
|
||
mixed = ek_model_mu(source or "") or (source is None and gecis_context.foreign_history(req))
|
||
# Format repair is stateless: the saved transcript can still contain
|
||
# foreign wrappers after a successful native return (or a restart).
|
||
if external or mixed or gecis_context.foreign(req):
|
||
try:
|
||
normalized = gecis_context.normalize(req)
|
||
except gecis_context.TransferError as e:
|
||
self._transfer_begin()
|
||
return self._transfer_fail(str(e))
|
||
# Small returning requests need no summarizer or local worker.
|
||
# Native opaque state must remain on its owner route. Its byte
|
||
# size is not a semantic token budget and cannot trigger a
|
||
# foreign summarizer, including when transfer state is cold.
|
||
if external or (mixed and not gecis_context.native_opaque(normalized)
|
||
and gecis_context.tokens(normalized) > int(32768 * 0.70)):
|
||
prepared, started = self._prepare(normalized, source)
|
||
if prepared is None:
|
||
return
|
||
if "compaction" in prepared or "compaction_items" in prepared:
|
||
if not started:
|
||
self._transfer_begin()
|
||
items = prepared.get("compaction_items") or [prepared["compaction"]]
|
||
events = [{"type": "response.output_item.done", "output_index": index, "item": item} for index, item in enumerate(items)]
|
||
events.append({"type": "response.completed", "response": {"id": "resp_aiturk_compact", "object": "response",
|
||
"status": "completed", "output": items}})
|
||
for event in events:
|
||
self._transfer_chunk(b"event: " + event["type"].encode() + b"\ndata: " + json.dumps(event, ensure_ascii=False).encode() + b"\n\n")
|
||
self.wfile.write(b"0\r\n\r\n")
|
||
self.wfile.flush()
|
||
return
|
||
normalized = prepared["request"]
|
||
else:
|
||
started = False
|
||
changed = normalized != req
|
||
body = json.dumps(normalized, ensure_ascii=False).encode() if changed else govde
|
||
if external and thread:
|
||
remember_transfer(scope, model)
|
||
self._native_return_scope = (scope, model) if thread and not external else None
|
||
return self._ilet(n, KOPRU if external else CHATGPT, yol, body,
|
||
f"{'KOPRU' if external else 'OZGUN'} {model}", rewritten=changed, started=started)
|
||
self._native_return_scope = (scope, model) if thread else None
|
||
return self._ilet(n, CHATGPT, yol, govde, f"OZGUN {model or '?'}")
|
||
if self.command == "GET" and "/models" in yol:
|
||
# Ek modeller listede gorunsun diye liste koprüden istenir; kopru cevap vermezse chatgpt.com'un listesi.
|
||
if self._ilet(n, KOPRU, yol, govde, "LISTE kopru", yedekli=True, sessiz=True):
|
||
return
|
||
return self._ilet(n, CHATGPT, yol, govde, "LISTE ozgun", sessiz=True)
|
||
return self._ilet(n, CHATGPT, yol, govde, "OZGUN diger", sessiz=True)
|
||
|
||
do_POST = _isle
|
||
do_GET = _isle
|
||
do_PUT = _isle
|
||
do_DELETE = _isle
|
||
do_PATCH = _isle
|
||
|
||
|
||
class Sunucu(http.server.ThreadingHTTPServer):
|
||
def __init__(self, *args, **kwargs):
|
||
self.failure_replays = terminal_stream.FailureReplays()
|
||
super().__init__(*args, **kwargs)
|
||
|
||
daemon_threads = True
|
||
# Shipped Python 3.11 defaults to only five pending connections. Model
|
||
# discovery, voice and simultaneous chats can arrive in the same burst.
|
||
request_queue_size = 128
|
||
allow_reuse_address = False # kapi doluysa sessizce yan yana dinlemek yerine acikca hata
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
sunucu = Sunucu(("127.0.0.1", PORT), H)
|
||
except OSError as e:
|
||
log(f"HATA: 127.0.0.1:{PORT} acilamadi ({e}); baska bir surec bu kapiyi kullaniyor olabilir.")
|
||
sys.exit(1)
|
||
log(f"ayirici :{PORT} Codex'in kendi modelleri -> {CHATGPT} ek modeller -> kopru :{KOPRU_PORT} (kod {KOD})")
|
||
threading.Thread(target=KOPRU_SUREC.bekci, daemon=True).start()
|
||
sunucu.serve_forever()
|