Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ee676d7c6 | ||
|
|
68a97b7489 | ||
|
|
6e91154f15 | ||
|
|
88e660768c | ||
|
|
bfd0ff196d | ||
|
|
1e0f2f9aea | ||
|
|
b560950f82 |
@@ -29,7 +29,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: https://gitea.twinpay.one/yilsem/ci-actions-checkout@11d5960a326750d5838078e36cf38b85af677262 # upstream v4.4.0
|
||||||
with:
|
with:
|
||||||
github-server-url: https://gitea.twinpay.one
|
github-server-url: https://gitea.twinpay.one
|
||||||
- name: Mobil TLS, profil ve baslatma regresyonlari
|
- name: Mobil TLS, profil ve baslatma regresyonlari
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
steps:
|
steps:
|
||||||
- name: Kaynagi al
|
- name: Kaynagi al
|
||||||
uses: actions/checkout@v4
|
uses: https://gitea.twinpay.one/yilsem/ci-actions-checkout@11d5960a326750d5838078e36cf38b85af677262 # upstream v4.4.0
|
||||||
with:
|
with:
|
||||||
github-server-url: https://gitea.twinpay.one
|
github-server-url: https://gitea.twinpay.one
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ Codex depends on it while `openai_base_url` points here.
|
|||||||
key read from the agent's `.env` on every catalog refresh (never written elsewhere).
|
key read from the agent's `.env` on every catalog refresh (never written elsewhere).
|
||||||
- `ide_catalog.py`: which IDE models and combos are added to Codex's model list and
|
- `ide_catalog.py`: which IDE models and combos are added to Codex's model list and
|
||||||
how they are named (`provider/model`, combos by bare name).
|
how they are named (`provider/model`, combos by bare name).
|
||||||
|
The public `/models` catalog supplies every OmniRoute chat model; a member does
|
||||||
|
not need a private local model allowlist. `/router/catalog` supplies only the
|
||||||
|
`kind` metadata used for sorting, never additional model IDs. Direct 9Router
|
||||||
|
entries and internal 9Router combos are excluded. Combos sort A–Z first, then
|
||||||
|
individual models by provider and name, including OpenAI subscription models.
|
||||||
|
Codex labels the first visible row `isDefault`; a saved model selection remains
|
||||||
|
unchanged. A failed or empty refresh keeps the last successful catalog.
|
||||||
- `ozet_kopru.py`: conversation summaries are produced as plain text, so a summary made
|
- `ozet_kopru.py`: conversation summaries are produced as plain text, so a summary made
|
||||||
by one provider stays readable after the user switches to another.
|
by one provider stays readable after the user switches to another.
|
||||||
- `gemini_context.py`, `resim_context.py`: request fixes for routed models.
|
- `gemini_context.py`, `resim_context.py`: request fixes for routed models.
|
||||||
|
|||||||
@@ -26,9 +26,42 @@ from resim_context import resimleri_sadelestir
|
|||||||
import ide_catalog
|
import ide_catalog
|
||||||
import ozet_kopru
|
import ozet_kopru
|
||||||
|
|
||||||
SURUM = "1"
|
SURUM = "5"
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
|
||||||
|
# interpreters the matching `zstandard` build ships next to this file.
|
||||||
|
_VENDOR = os.path.join(HERE, "vendor", "cp%d%d-win_amd64" % sys.version_info[:2])
|
||||||
|
if os.path.isdir(_VENDOR):
|
||||||
|
sys.path.insert(0, _VENDOR)
|
||||||
|
|
||||||
|
|
||||||
|
def _zstd_decompress(body):
|
||||||
|
try:
|
||||||
|
from compression import zstd as _z
|
||||||
|
return _z.decompress(body)
|
||||||
|
except ImportError:
|
||||||
|
import zstandard
|
||||||
|
return zstandard.ZstdDecompressor().decompressobj().decompress(body)
|
||||||
|
|
||||||
|
|
||||||
|
def _zstd_ready():
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
from compression import zstd as _z
|
||||||
|
sample = _z.compress(b"aiturk")
|
||||||
|
except ImportError:
|
||||||
|
import zstandard
|
||||||
|
sample = zstandard.ZstdCompressor().compress(b"aiturk")
|
||||||
|
return _zstd_decompress(sample) == b"aiturk"
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Without zstd every Codex request would fail; the status endpoint then reports
|
||||||
|
# ok=false so that the IDE never points Codex at this bridge.
|
||||||
|
ZSTD_OK = _zstd_ready()
|
||||||
|
|
||||||
|
|
||||||
def _arg(name, env):
|
def _arg(name, env):
|
||||||
"""--name <value> from the command line, else the environment variable."""
|
"""--name <value> from the command line, else the environment variable."""
|
||||||
@@ -150,14 +183,8 @@ def load_ide_catalog():
|
|||||||
ROUTES[IDE_ROUTE] = {"url": base + "/responses", "key": anahtar, "flatten_tools": True}
|
ROUTES[IDE_ROUTE] = {"url": base + "/responses", "key": anahtar, "flatten_tools": True}
|
||||||
clone = (CFG.get("nine_router_catalog") or {}).get("clone_from", "gpt-6-astra")
|
clone = (CFG.get("nine_router_catalog") or {}).get("clone_from", "gpt-6-astra")
|
||||||
levels = (CFG.get("nine_router_catalog") or {}).get("reasoning_levels")
|
levels = (CFG.get("nine_router_catalog") or {}).get("reasoning_levels")
|
||||||
# Secicide yalniz "saglayici/model" gorunur. 9Router listesinde ayni adla zaten duran model
|
# Gorunen adlari ayirt et; farkli kimlikteki modeller adlari benziyor diye silinmez.
|
||||||
# ikinci kez eklenmez (ayni modelin iki satir olmamasi icin).
|
|
||||||
var_olan = {m.get("display_name", "").lower() for m in extra_models}
|
var_olan = {m.get("display_name", "").lower() for m in extra_models}
|
||||||
for g in girdiler:
|
|
||||||
slug = IDE_PREFIX + g["id"]
|
|
||||||
g["kisa"] = ide_catalog.kisa_ad(g["ad"])
|
|
||||||
# "ad (2)": ayni modelin gecitteki ikinci yolu; taban ad listede varsa o da eklenmez.
|
|
||||||
girdiler = [g for g in girdiler if re.sub(r" \(\d+\)$", "", g["kisa"]).lower() not in var_olan]
|
|
||||||
adlar = ide_catalog.adlandir([(g["id"], g["ad"]) for g in girdiler], dolu=var_olan)
|
adlar = ide_catalog.adlandir([(g["id"], g["ad"]) for g in girdiler], dolu=var_olan)
|
||||||
for g in girdiler:
|
for g in girdiler:
|
||||||
slug = IDE_PREFIX + g["id"]
|
slug = IDE_PREFIX + g["id"]
|
||||||
@@ -165,7 +192,8 @@ def load_ide_catalog():
|
|||||||
continue
|
continue
|
||||||
extra_models.append({"slug": slug, "display_name": adlar[g["id"]],
|
extra_models.append({"slug": slug, "display_name": adlar[g["id"]],
|
||||||
"description": f"AITURK IDE: {g['ad']}", "route": IDE_ROUTE, "target": g["id"],
|
"description": f"AITURK IDE: {g['ad']}", "route": IDE_ROUTE, "target": g["id"],
|
||||||
"clone_from": clone, "priority": 300 + n, "reasoning_levels": levels})
|
"clone_from": clone, "priority": 300 + n, "reasoning_levels": levels,
|
||||||
|
"kind": g["kind"]})
|
||||||
model_map[slug] = (IDE_ROUTE, g["id"], None)
|
model_map[slug] = (IDE_ROUTE, g["id"], None)
|
||||||
n += 1
|
n += 1
|
||||||
else:
|
else:
|
||||||
@@ -468,10 +496,7 @@ def decompress(body, enc):
|
|||||||
if enc == "deflate":
|
if enc == "deflate":
|
||||||
import zlib; return zlib.decompress(body)
|
import zlib; return zlib.decompress(body)
|
||||||
if enc == "zstd":
|
if enc == "zstd":
|
||||||
try:
|
return _zstd_decompress(body)
|
||||||
from compression import zstd as _z; return _z.decompress(body)
|
|
||||||
except ImportError:
|
|
||||||
import zstandard; return zstandard.ZstdDecompressor().decompressobj().decompress(body)
|
|
||||||
if enc == "br":
|
if enc == "br":
|
||||||
import brotli; return brotli.decompress(body)
|
import brotli; return brotli.decompress(body)
|
||||||
return body
|
return body
|
||||||
@@ -533,9 +558,18 @@ class H(http.server.BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
def _control(self, path):
|
def _control(self, path):
|
||||||
"""Local status/stop for AITURK IDE. Loopback only; carries no secrets."""
|
"""Local status/stop for AITURK IDE. Loopback only; carries no secrets."""
|
||||||
|
if self.command == "POST" and path.rstrip("/").endswith("/refresh"):
|
||||||
|
# The IDE asks for this after a sign-in or a settings change, so the model
|
||||||
|
# list does not wait for the next periodic refresh.
|
||||||
|
try:
|
||||||
|
load_ide_catalog()
|
||||||
|
except Exception as e:
|
||||||
|
log(f"katalog yenileme hatasi: {e}")
|
||||||
|
path = "/aiturk/codex-baglama/status"
|
||||||
|
self.command = "GET"
|
||||||
if self.command == "GET" and path.rstrip("/").endswith("/status"):
|
if self.command == "GET" and path.rstrip("/").endswith("/status"):
|
||||||
durum, _, sayi = (IDE_DURUM[0] or "kapali, 0 model").partition(", ")
|
durum, _, sayi = (IDE_DURUM[0] or "kapali, 0 model").partition(", ")
|
||||||
return self._send_json(200, {"ok": True, "surum": SURUM, "ide_durum": durum,
|
return self._send_json(200, {"ok": ZSTD_OK, "surum": SURUM, "zstd": ZSTD_OK, "ide_durum": durum,
|
||||||
"ide_model": int((sayi.split() or ["0"])[0]),
|
"ide_model": int((sayi.split() or ["0"])[0]),
|
||||||
"ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False))})
|
"ozet": bool((CFG.get("ozet_kopru") or {}).get("enabled", False))})
|
||||||
if self.command == "POST" and path.rstrip("/").endswith("/stop"):
|
if self.command == "POST" and path.rstrip("/").endswith("/stop"):
|
||||||
@@ -685,6 +719,8 @@ class H(http.server.BaseHTTPRequestHandler):
|
|||||||
if not m.get("base_instructions") and not has_tmpl:
|
if not m.get("base_instructions") and not has_tmpl:
|
||||||
m.update(saved)
|
m.update(saved)
|
||||||
models.append(m); changed += 1
|
models.append(m); changed += 1
|
||||||
|
if EXTRA_MODELS:
|
||||||
|
ide_catalog.sirala_codex(models, EXTRA_MODELS)
|
||||||
out = json.dumps(data, ensure_ascii=False).encode("utf-8")
|
out = json.dumps(data, ensure_ascii=False).encode("utf-8")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f"{tag} model listesi degistirilemedi: {e}")
|
log(f"{tag} model listesi degistirilemedi: {e}")
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ anahtar IDE'nin kendi .env dosyasindan her yenilemede okunur, bridge.json'a yazi
|
|||||||
Ac/kapat: %LOCALAPPDATA%\\TurkServis\\AITURK-IDE\\codex-baglama.json
|
Ac/kapat: %LOCALAPPDATA%\\TurkServis\\AITURK-IDE\\codex-baglama.json
|
||||||
{"enabled": true, "kapsam": "ide"}
|
{"enabled": true, "kapsam": "ide"}
|
||||||
Dosya yoksa ACIK sayilir (kullanici karari, 9 Ekim 2026: varsayilan acik).
|
Dosya yoksa ACIK sayilir (kullanici karari, 9 Ekim 2026: varsayilan acik).
|
||||||
kapsam "ide" : IDE ayarinda tanimli modeller + adlandirilmis combolar (varsayilan)
|
kapsam "ide" ve "hepsi": gecidin uye katalogundaki OmniRoute sohbet modelleri.
|
||||||
kapsam "hepsi" : gecitteki butun sohbet modelleri (1000+; Codex listesi cok buyur)
|
Yerel config'teki model adlari uye katalogunu sinirlamaz.
|
||||||
"""
|
"""
|
||||||
import json, os, re, urllib.request
|
import json, os, re, urllib.request
|
||||||
|
|
||||||
@@ -84,23 +84,33 @@ def ide_yapilandirma(agent=AGENT):
|
|||||||
return base.rstrip("/"), varsayilan, adlar
|
return base.rstrip("/"), varsayilan, adlar
|
||||||
|
|
||||||
|
|
||||||
|
DOKUZ_ISARET = "9R · "
|
||||||
|
|
||||||
|
|
||||||
|
def _isaret(ad):
|
||||||
|
"""Gecidin dogrudan 9Router'dan gelen tekil modele koydugu kisa isaret korunur (kullanici karari,
|
||||||
|
9 Ekim 2026: "9Router" yazisi uzun, hic yazmamak da sorun; "9R" olarak kisa gorunsun)."""
|
||||||
|
return DOKUZ_ISARET if (ad or "").startswith(DOKUZ_ISARET) else ""
|
||||||
|
|
||||||
|
|
||||||
def kisa_ad(ad):
|
def kisa_ad(ad):
|
||||||
"""Codex secicisinde gorunecek ad: yalniz kullanicinin ekledigi saglayici + model adi
|
"""Codex secicisinde gorunecek ad: yalniz kullanicinin ekledigi saglayici + model adi
|
||||||
(kullanici istegi, 9 Ekim 2026). "OmniRoute · " / "9Router · " gibi gecit onekleri ve aradaki
|
(kullanici istegi, 9 Ekim 2026). "OmniRoute · " / "9Router · " gibi gecit onekleri ve aradaki
|
||||||
saglayici halkalari gosterilmez: "nano/qwen/qwen3.8-27b" -> "nano/qwen3.8-27b",
|
saglayici halkalari gosterilmez: "nano/qwen/qwen3.8-27b" -> "nano/qwen3.8-27b",
|
||||||
"OmniRoute · ag/gemini-3.8-flash" -> "ag/gemini-3.8-flash", combo adi oldugu gibi kalir."""
|
"OmniRoute · ag/gemini-3.8-flash" -> "ag/gemini-3.8-flash", combo adi oldugu gibi kalir."""
|
||||||
|
on = _isaret(ad)
|
||||||
ad = (ad or "").split(" · ")[-1].strip()
|
ad = (ad or "").split(" · ")[-1].strip()
|
||||||
parcalar = [p for p in ad.split("/") if p]
|
parcalar = [p for p in ad.split("/") if p]
|
||||||
if len(parcalar) <= 2:
|
if len(parcalar) <= 2:
|
||||||
return "/".join(parcalar) or ad
|
return on + ("/".join(parcalar) or ad)
|
||||||
return parcalar[0] + "/" + parcalar[-1]
|
return on + parcalar[0] + "/" + parcalar[-1]
|
||||||
|
|
||||||
|
|
||||||
def adlandir(girdiler, dolu=()):
|
def adlandir(girdiler, dolu=()):
|
||||||
"""[(kimlik, tam_ad)] -> {kimlik: gorunen_ad}. Kisa adlar cakisirsa aradaki halkalar geri konur;
|
"""[(kimlik, tam_ad)] -> {kimlik: gorunen_ad}. Kisa adlar cakisirsa aradaki halkalar geri konur;
|
||||||
yine cakisirsa kimligin sonu eklenir. `dolu`: baska kaynaktan gelen, zaten kullanilan adlar."""
|
yine cakisirsa kimligin sonu eklenir. `dolu`: baska kaynaktan gelen, zaten kullanilan adlar."""
|
||||||
kisa = {k: kisa_ad(a) for k, a in girdiler}
|
kisa = {k: kisa_ad(a) for k, a in girdiler}
|
||||||
tam = {k: (a or "").split(" · ")[-1].strip() for k, a in girdiler}
|
tam = {k: _isaret(a) + (a or "").split(" · ")[-1].strip() for k, a in girdiler}
|
||||||
sayac = {}
|
sayac = {}
|
||||||
for v in list(kisa.values()) + list(dolu):
|
for v in list(kisa.values()) + list(dolu):
|
||||||
sayac[v.lower()] = sayac.get(v.lower(), 0) + 1
|
sayac[v.lower()] = sayac.get(v.lower(), 0) + 1
|
||||||
@@ -114,10 +124,10 @@ def adlandir(girdiler, dolu=()):
|
|||||||
return sonuc
|
return sonuc
|
||||||
|
|
||||||
|
|
||||||
def sec(data, tanimli, kapsam="ide"):
|
def sec(data, tanimli=(), kapsam="ide", turler=None):
|
||||||
"""Gecit katalogundan Codex'e eklenecek girdiler: [{'id','ad'}], sirali ve tekrarsiz.
|
"""Gecidin uye katalogundan girdiler: [{'id','ad','kind'}], sirali ve tekrarsiz.
|
||||||
Yalniz sohbet modelleri; resim/ses/video/embedding uretenler eklenmez."""
|
Yalniz sohbet modelleri; resim/ses/video/embedding uretenler eklenmez."""
|
||||||
tanimli = {a.lower() for a in tanimli if a}
|
turler = turler or {}
|
||||||
secilen, gorulen = [], set()
|
secilen, gorulen = [], set()
|
||||||
for m in data:
|
for m in data:
|
||||||
mid = m.get("id") or ""
|
mid = m.get("id") or ""
|
||||||
@@ -125,14 +135,56 @@ def sec(data, tanimli, kapsam="ide"):
|
|||||||
if not mid or mid in gorulen or "chat" not in (ts.get("capabilities") or ["chat"]):
|
if not mid or mid in gorulen or "chat" not in (ts.get("capabilities") or ["chat"]):
|
||||||
continue
|
continue
|
||||||
gorunen = ts.get("display_name") or mid
|
gorunen = ts.get("display_name") or mid
|
||||||
kisa = gorunen.split(" · ")[-1].strip()
|
kind = turler.get(mid) or ts.get("kind") or "model"
|
||||||
combo = "/" not in kisa # saglayici/model bicimi degil: adlandirilmis combo ya da takma ad
|
# Tek uye yolu: 9Router -> OmniRoute -> TurkServis -> Codex.
|
||||||
if kapsam != "hepsi" and not combo and kisa.lower() not in tanimli and mid.lower() not in tanimli:
|
# Dogrudan 9Router satirlari ayni modelin ikinci kopyasi olarak eklenmez.
|
||||||
|
if ts.get("source") != "omniroute":
|
||||||
|
continue
|
||||||
|
# Combo adi biciminden tahmin edilmez; duz adli Agnes/Bekci modelleri de vardir.
|
||||||
|
if kind == "combo" and mid.lower().startswith("9router-"):
|
||||||
continue
|
continue
|
||||||
gorulen.add(mid)
|
gorulen.add(mid)
|
||||||
secilen.append({"id": mid, "ad": gorunen, "sira": ts.get("order") or 0})
|
secilen.append({"id": mid, "ad": gorunen, "kind": kind, "sira": ts.get("order") or 0})
|
||||||
secilen.sort(key=lambda s: (s["sira"], s["id"]))
|
secilen.sort(key=lambda s: (s["sira"], s["id"]))
|
||||||
return [{"id": s["id"], "ad": s["ad"]} for s in secilen]
|
# Gecit ayni adin ikinci yoluna "ad (2)" der; birincisi listede yoksa sayac anlamsizdir.
|
||||||
|
adlar = {s["ad"].split(" · ")[-1].strip().lower() for s in secilen}
|
||||||
|
for s in secilen:
|
||||||
|
taban = re.sub(r" \(\d+\)$", "", s["ad"])
|
||||||
|
if taban != s["ad"] and taban.split(" · ")[-1].strip().lower() not in adlar:
|
||||||
|
adlar.add(taban.split(" · ")[-1].strip().lower())
|
||||||
|
s["ad"] = taban
|
||||||
|
return [{"id": s["id"], "ad": s["ad"], "kind": s["kind"]} for s in secilen]
|
||||||
|
|
||||||
|
|
||||||
|
def sirala_codex(modeller, ekler):
|
||||||
|
"""Tum listeyi siralar: combo A-Z, sonra saglayici A-Z / model A-Z.
|
||||||
|
Kimlik ve yetenek alanlari korunur; kayitli model secimine dokunulmaz.
|
||||||
|
Codex'in isDefault etiketi kendi davranisi geregi ilk gorunur satira verilir."""
|
||||||
|
bilgiler = {m["slug"]: m for m in ekler}
|
||||||
|
|
||||||
|
def anahtar(m):
|
||||||
|
slug = m.get("slug", "")
|
||||||
|
bilgi = bilgiler.get(slug, {})
|
||||||
|
ad = kisa_ad(m.get("display_name") or slug)
|
||||||
|
if ad.startswith(DOKUZ_ISARET):
|
||||||
|
ad = ad[len(DOKUZ_ISARET):]
|
||||||
|
if bilgi.get("kind") == "combo":
|
||||||
|
return (0, ad.casefold(), "", slug)
|
||||||
|
if slug.startswith("ide/"):
|
||||||
|
mid = slug[len("ide/"):]
|
||||||
|
if mid.startswith("9router-"):
|
||||||
|
mid = mid[len("9router-"):]
|
||||||
|
saglayici, _, model = ad.partition("/")
|
||||||
|
if not model:
|
||||||
|
saglayici, _, _ = mid.partition("-")
|
||||||
|
model = ad
|
||||||
|
else:
|
||||||
|
saglayici, model = "OpenAI", ad
|
||||||
|
return (1, saglayici.casefold(), model.casefold(), slug)
|
||||||
|
|
||||||
|
modeller.sort(key=anahtar)
|
||||||
|
for n, model in enumerate(modeller):
|
||||||
|
model["priority"] = n
|
||||||
|
|
||||||
|
|
||||||
def yukle(ua="AstraBridge/1.0 (codex)", timeout=30):
|
def yukle(ua="AstraBridge/1.0 (codex)", timeout=30):
|
||||||
@@ -146,5 +198,19 @@ def yukle(ua="AstraBridge/1.0 (codex)", timeout=30):
|
|||||||
return "anahtar-yok", "", "", []
|
return "anahtar-yok", "", "", []
|
||||||
base, varsayilan, adlar = ide_yapilandirma()
|
base, varsayilan, adlar = ide_yapilandirma()
|
||||||
req = urllib.request.Request(base + "/models", headers={"Authorization": f"Bearer {anahtar}", "User-Agent": ua})
|
req = urllib.request.Request(base + "/models", headers={"Authorization": f"Bearer {anahtar}", "User-Agent": ua})
|
||||||
data = json.load(urllib.request.urlopen(req, timeout=timeout)).get("data", [])
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||||
return "tamam", base, anahtar, sec(data, adlar + [varsayilan], ayar["kapsam"])
|
data = json.load(resp).get("data")
|
||||||
|
if not isinstance(data, list) or not data:
|
||||||
|
raise ValueError("Gecit bos veya gecersiz model katalogu dondurdu")
|
||||||
|
req = urllib.request.Request(base + "/router/catalog", headers={"Authorization": f"Bearer {anahtar}", "User-Agent": ua})
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||||
|
metadata = json.load(resp).get("models")
|
||||||
|
if not isinstance(metadata, list) or not metadata:
|
||||||
|
raise ValueError("Gecit model turlerini dondurmedi")
|
||||||
|
# Bu ucta uye listesinde olmayan ic kayitlar da bulunur. Yalniz tur bilgisi alinir;
|
||||||
|
# modele erisim listesi her zaman /models yanitidir.
|
||||||
|
turler = {m["id"]: m["kind"] for m in metadata if m.get("id") and m.get("kind") in ("combo", "model")}
|
||||||
|
girdiler = sec(data, adlar + [varsayilan], ayar["kapsam"], turler)
|
||||||
|
if not girdiler:
|
||||||
|
raise ValueError("Gecitte kullanilabilir OmniRoute sohbet modeli yok")
|
||||||
|
return "tamam", base, anahtar, girdiler
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import json, os, tempfile, unittest
|
|||||||
import ide_catalog as c
|
import ide_catalog as c
|
||||||
|
|
||||||
|
|
||||||
def girdi(mid, ad, yetenek=("chat",), sira=0):
|
def girdi(mid, ad, yetenek=("chat",), sira=0, kaynak="omniroute", kind="model"):
|
||||||
return {"id": mid, "turkservis": {"display_name": ad, "capabilities": list(yetenek), "order": sira}}
|
return {"id": mid, "turkservis": {"display_name": ad, "capabilities": list(yetenek), "order": sira, "source": kaynak, "kind": kind}}
|
||||||
|
|
||||||
|
|
||||||
KATALOG = [
|
KATALOG = [
|
||||||
@@ -11,8 +11,8 @@ KATALOG = [
|
|||||||
girdi("9router-cc-opus", "OmniRoute · cc/claude-opus-5-5", sira=2),
|
girdi("9router-cc-opus", "OmniRoute · cc/claude-opus-5-5", sira=2),
|
||||||
girdi("9router-nano-x", "OmniRoute · nano/x", sira=1),
|
girdi("9router-nano-x", "OmniRoute · nano/x", sira=1),
|
||||||
girdi("gorsel", "OmniRoute · gpt-image-1", ("image",)),
|
girdi("gorsel", "OmniRoute · gpt-image-1", ("image",)),
|
||||||
girdi("9router-Takim-Agir", "OmniRoute · Takim-Agir", sira=9),
|
girdi("9router-Takim-Agir", "OmniRoute · Takim-Agir", sira=9, kind="combo"),
|
||||||
girdi("9router-Takim-Agir-6e65dba5", "OmniRoute · Takim-Agir", sira=8),
|
girdi("9router-Takim-Agir-6e65dba5", "OmniRoute · Takim-Agir", sira=8, kind="combo"),
|
||||||
girdi("Ajan-Arac", "OmniRoute · Ajan-Arac (tekrar)"),
|
girdi("Ajan-Arac", "OmniRoute · Ajan-Arac (tekrar)"),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -52,14 +52,39 @@ class Yapilandirma(unittest.TestCase):
|
|||||||
class Secim(unittest.TestCase):
|
class Secim(unittest.TestCase):
|
||||||
def test_ide_kapsami(self):
|
def test_ide_kapsami(self):
|
||||||
s = c.sec(KATALOG, ["cc/claude-opus-5-5"], "ide")
|
s = c.sec(KATALOG, ["cc/claude-opus-5-5"], "ide")
|
||||||
# Tanimli model + adlandirilmis combolar; tanimsiz saglayici modeli ve gorsel modeli yok.
|
# Uye katalogu yerel config'teki adlarla sinirlanmaz.
|
||||||
self.assertEqual([x["id"] for x in s], ["9router-cc-opus", "Ajan-Arac", "9router-Takim-Agir-6e65dba5", "9router-Takim-Agir"])
|
self.assertEqual([x["id"] for x in s], ["9router-nano-x", "9router-cc-opus", "Ajan-Arac"])
|
||||||
|
|
||||||
|
def test_9router_combolari_gosterilmez(self):
|
||||||
|
katalog = [girdi("9router-Takim-Agir-6e65dba5", "Takim-Agir", kaynak="9router", sira=1),
|
||||||
|
girdi("9router-Takim-Agir", "Takim-Agir (2)", sira=2, kind="combo"),
|
||||||
|
girdi("Takim-Agir", "Takim-Agir (3)", sira=3),
|
||||||
|
girdi("Ajan-Arac", "Ajan-Arac", sira=4),
|
||||||
|
girdi("9router-nano-x", "nano/x", kaynak="9router", sira=5)]
|
||||||
|
s = c.sec(katalog, ["nano/x"], "ide")
|
||||||
|
# 9Router combosu ve gecidin 9router saglayicisindaki kopyasi yok; OmniRoute'un kendi combosu
|
||||||
|
# sayacsiz adiyla kalir. Dogrudan 9Router tekilleri de eklenmez.
|
||||||
|
self.assertEqual([(x["id"], x["ad"]) for x in s],
|
||||||
|
[("Takim-Agir", "Takim-Agir"), ("Ajan-Arac", "Ajan-Arac")])
|
||||||
|
|
||||||
|
def test_9router_modelleri_yalniz_omniroute_uzerinden_eklenir(self):
|
||||||
|
katalog = [girdi("9router-nano-x-aaaa1111", "nano/x", kaynak="9router", sira=1),
|
||||||
|
girdi("9router-nano-x", "nano/x (2)", sira=2),
|
||||||
|
girdi("9router-ocg-y-bbbb2222", "ocg/y", kaynak="9router", sira=3),
|
||||||
|
girdi("vag-z", "vag/z", sira=4),
|
||||||
|
girdi("9router-ses", "nano/ses", ("tts",), kaynak="9router", sira=5),
|
||||||
|
girdi("9router-Takim-Agir-6e65dba5", "Takim-Agir", kaynak="9router", sira=6)]
|
||||||
|
s = c.sec(katalog, [], "ide")
|
||||||
|
# Yerel model tanimi gerekmez; iki kaynak yerine yalniz OmniRoute yolu gelir.
|
||||||
|
self.assertEqual([(x["id"], x["ad"]) for x in s],
|
||||||
|
[("9router-nano-x", "nano/x"), ("vag-z", "vag/z")])
|
||||||
|
|
||||||
def test_gorunen_ad_yalniz_saglayici_ve_model(self):
|
def test_gorunen_ad_yalniz_saglayici_ve_model(self):
|
||||||
for tam, beklenen in [("OmniRoute · ag/gemini-3.8-flash", "ag/gemini-3.8-flash"),
|
for tam, beklenen in [("OmniRoute · ag/gemini-3.8-flash", "ag/gemini-3.8-flash"),
|
||||||
("nano/qwen/qwen3.8-27b-obliterated:thinking", "nano/qwen3.8-27b-obliterated:thinking"),
|
("nano/qwen/qwen3.8-27b-obliterated:thinking", "nano/qwen3.8-27b-obliterated:thinking"),
|
||||||
("9Router · Nous-Research/inclusionai/ling-3.1-flash", "Nous-Research/ling-3.1-flash"),
|
("9Router · Nous-Research/inclusionai/ling-3.1-flash", "Nous-Research/ling-3.1-flash"),
|
||||||
("cf/@cf/qwen/qwq-32b", "cf/qwq-32b"),
|
("cf/@cf/qwen/qwq-32b", "cf/qwq-32b"),
|
||||||
|
("9R · nano/qwen/qwen3.8-27b", "9R · nano/qwen3.8-27b"), ("9R · ag/gemini", "9R · ag/gemini"),
|
||||||
("OmniRoute · Ajan-Arac", "Ajan-Arac"), ("cc/claude-opus-5-5", "cc/claude-opus-5-5")]:
|
("OmniRoute · Ajan-Arac", "Ajan-Arac"), ("cc/claude-opus-5-5", "cc/claude-opus-5-5")]:
|
||||||
self.assertEqual(c.kisa_ad(tam), beklenen, tam)
|
self.assertEqual(c.kisa_ad(tam), beklenen, tam)
|
||||||
|
|
||||||
@@ -74,7 +99,7 @@ class Secim(unittest.TestCase):
|
|||||||
|
|
||||||
def test_hepsi_kapsami_yalniz_sohbet(self):
|
def test_hepsi_kapsami_yalniz_sohbet(self):
|
||||||
s = c.sec(KATALOG, [], "hepsi")
|
s = c.sec(KATALOG, [], "hepsi")
|
||||||
self.assertEqual(len(s), 5)
|
self.assertEqual(len(s), 3) # iki 9Router combosu hicbir kapsamda gosterilmez
|
||||||
self.assertNotIn("gorsel", [x["id"] for x in s])
|
self.assertNotIn("gorsel", [x["id"] for x in s])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
"""The shipped IDE must use the same public OmniRoute catalog as every member."""
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
import ide_catalog as c
|
||||||
|
|
||||||
|
|
||||||
|
def row(mid, name, source="omniroute", capabilities=("chat",)):
|
||||||
|
return {"id": mid, "turkservis": {"display_name": name, "source": source,
|
||||||
|
"capabilities": list(capabilities)}}
|
||||||
|
|
||||||
|
|
||||||
|
class MemberCatalog(unittest.TestCase):
|
||||||
|
def test_metadata_classifies_only_public_models(self):
|
||||||
|
public = [row("plain", "Plain"), row("team", "Team")]
|
||||||
|
metadata = [{"id": "plain", "kind": "model"}, {"id": "team", "kind": "combo"},
|
||||||
|
{"id": "internal", "kind": "combo"}]
|
||||||
|
replies = [io.BytesIO(json.dumps({"data": public}).encode()),
|
||||||
|
io.BytesIO(json.dumps({"models": metadata}).encode())]
|
||||||
|
with patch.object(c, "ayar_oku", return_value={"enabled": True, "kapsam": "ide"}), \
|
||||||
|
patch.object(c, "anahtar_oku", return_value="test-key"), \
|
||||||
|
patch.object(c, "ide_yapilandirma", return_value=("https://example.test/v1", "", [])), \
|
||||||
|
patch.object(c.urllib.request, "urlopen", side_effect=replies):
|
||||||
|
status, _, _, selected = c.yukle()
|
||||||
|
self.assertEqual(status, "tamam")
|
||||||
|
self.assertEqual({m["id"]: m["kind"] for m in selected}, {"plain": "model", "team": "combo"})
|
||||||
|
|
||||||
|
def test_empty_catalog_is_a_refresh_failure(self):
|
||||||
|
with patch.object(c, "ayar_oku", return_value={"enabled": True, "kapsam": "ide"}), \
|
||||||
|
patch.object(c, "anahtar_oku", return_value="test-key"), \
|
||||||
|
patch.object(c, "ide_yapilandirma", return_value=("https://example.test/v1", "", [])), \
|
||||||
|
patch.object(c.urllib.request, "urlopen", return_value=io.BytesIO(b'{"data": []}')):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
c.yukle()
|
||||||
|
|
||||||
|
def test_public_omniroute_models_do_not_need_private_local_configuration(self):
|
||||||
|
rows = [row("9router-nano-x", "9R · nano/x"), row("agnes-flat", "Agnes Flat"),
|
||||||
|
row("Ajan-Arac", "Ajan-Arac"), row("image", "image", capabilities=("image",))]
|
||||||
|
self.assertEqual({m["id"] for m in c.sec(rows, [])},
|
||||||
|
{"9router-nano-x", "agnes-flat", "Ajan-Arac"})
|
||||||
|
|
||||||
|
def test_direct_route_is_never_added_beside_omniroute(self):
|
||||||
|
rows = [row("9router-nano-x-direct", "9R · nano/x", "9router"),
|
||||||
|
row("9router-nano-x", "9R · nano/x")]
|
||||||
|
self.assertEqual([m["id"] for m in c.sec(rows, ["nano/x"], "hepsi")], ["9router-nano-x"])
|
||||||
|
|
||||||
|
def test_model_order_uses_combo_metadata_and_preserves_every_model(self):
|
||||||
|
models = [{"slug": "gpt-z", "display_name": "GPT Z", "priority": 1},
|
||||||
|
{"slug": "ide/bekci-kurnaz", "display_name": "kurnaz", "priority": 2},
|
||||||
|
{"slug": "ide/Z-Team", "display_name": "Z-Team", "priority": 5},
|
||||||
|
{"slug": "ide/9router-cc-opus", "display_name": "9R · cc/opus", "priority": 3},
|
||||||
|
{"slug": "ide/A-Team", "display_name": "A-Team", "priority": 4}]
|
||||||
|
extras = [{"slug": m["slug"], "kind": "combo" if m["slug"].endswith("Team") else "model"}
|
||||||
|
for m in models if m["slug"].startswith("ide/")]
|
||||||
|
c.sirala_codex(models, extras)
|
||||||
|
self.assertEqual([m["slug"] for m in models],
|
||||||
|
["ide/A-Team", "ide/Z-Team", "ide/bekci-kurnaz", "ide/9router-cc-opus", "gpt-z"])
|
||||||
|
self.assertEqual([m["priority"] for m in models], list(range(5)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""Codex compresses request bodies with zstd; the bridge must be able to read them."""
|
||||||
|
import ast
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
tree = ast.parse(Path(__file__).with_name("bridge.py").read_text(encoding="utf-8"))
|
||||||
|
wanted = {"_zstd_decompress", "_zstd_ready", "decompress"}
|
||||||
|
env = {}
|
||||||
|
exec(compile(ast.Module(body=[n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name in wanted],
|
||||||
|
type_ignores=[]), "bridge.py", "exec"), env)
|
||||||
|
|
||||||
|
|
||||||
|
class Zstd(unittest.TestCase):
|
||||||
|
def test_self_check_matches_reality(self):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
from compression import zstd as z
|
||||||
|
packed = z.compress(b"govde" * 50)
|
||||||
|
except ImportError:
|
||||||
|
import zstandard
|
||||||
|
packed = zstandard.ZstdCompressor().compress(b"govde" * 50)
|
||||||
|
except ImportError:
|
||||||
|
self.assertFalse(env["_zstd_ready"]()) # no zstd here: the bridge must say so
|
||||||
|
return
|
||||||
|
self.assertTrue(env["_zstd_ready"]())
|
||||||
|
self.assertEqual(env["decompress"](packed, "zstd"), b"govde" * 50)
|
||||||
|
|
||||||
|
def test_other_encodings(self):
|
||||||
|
import gzip
|
||||||
|
self.assertEqual(env["decompress"](gzip.compress(b"x"), "gzip"), b"x")
|
||||||
|
self.assertEqual(env["decompress"](b"duz", None), b"duz")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2016, Gregory Szorc
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
# Copyright (c) 2017-present, Gregory Szorc
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# This software may be modified and distributed under the terms
|
||||||
|
# of the BSD license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
# ruff: noqa: F403, F405
|
||||||
|
|
||||||
|
"""Python interface to the Zstandard (zstd) compression library."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
# This module serves 2 roles:
|
||||||
|
#
|
||||||
|
# 1) Export the C or CFFI "backend" through a central module.
|
||||||
|
# 2) Implement additional functionality built on top of C or CFFI backend.
|
||||||
|
import builtins
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 12):
|
||||||
|
from collections.abc import Buffer
|
||||||
|
else:
|
||||||
|
from typing import ByteString as Buffer
|
||||||
|
|
||||||
|
# Some Python implementations don't support C extensions. That's why we have
|
||||||
|
# a CFFI implementation in the first place. The code here import one of our
|
||||||
|
# "backends" then re-exports the symbols from this module. For convenience,
|
||||||
|
# we support falling back to the CFFI backend if the C extension can't be
|
||||||
|
# imported. But for performance reasons, we only do this on unknown Python
|
||||||
|
# implementation. Notably, for CPython we require the C extension by default.
|
||||||
|
# Because someone will inevitably want special behavior, the behavior is
|
||||||
|
# configurable via an environment variable. A potentially better way to handle
|
||||||
|
# this is to import a special ``__importpolicy__`` module or something
|
||||||
|
# defining a variable and `setup.py` could write the file with whatever
|
||||||
|
# policy was specified at build time. Until someone needs it, we go with
|
||||||
|
# the hacky but simple environment variable approach.
|
||||||
|
_module_policy = os.environ.get(
|
||||||
|
"PYTHON_ZSTANDARD_IMPORT_POLICY", "default"
|
||||||
|
).strip()
|
||||||
|
|
||||||
|
if _module_policy == "default":
|
||||||
|
if platform.python_implementation() in ("CPython",):
|
||||||
|
from .backend_c import * # type: ignore
|
||||||
|
|
||||||
|
backend = "cext"
|
||||||
|
elif platform.python_implementation() in ("PyPy",):
|
||||||
|
from .backend_cffi import * # type: ignore
|
||||||
|
|
||||||
|
backend = "cffi"
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from .backend_c import *
|
||||||
|
|
||||||
|
backend = "cext"
|
||||||
|
except ImportError:
|
||||||
|
from .backend_cffi import *
|
||||||
|
|
||||||
|
backend = "cffi"
|
||||||
|
elif _module_policy == "cffi_fallback":
|
||||||
|
try:
|
||||||
|
from .backend_c import *
|
||||||
|
|
||||||
|
backend = "cext"
|
||||||
|
except ImportError:
|
||||||
|
from .backend_cffi import *
|
||||||
|
|
||||||
|
backend = "cffi"
|
||||||
|
elif _module_policy == "rust":
|
||||||
|
from .backend_rust import * # type: ignore
|
||||||
|
|
||||||
|
backend = "rust"
|
||||||
|
elif _module_policy == "cext":
|
||||||
|
from .backend_c import *
|
||||||
|
|
||||||
|
backend = "cext"
|
||||||
|
elif _module_policy == "cffi":
|
||||||
|
from .backend_cffi import *
|
||||||
|
|
||||||
|
backend = "cffi"
|
||||||
|
else:
|
||||||
|
raise ImportError(
|
||||||
|
"unknown module import policy: %s; use default, cffi_fallback, "
|
||||||
|
"cext, or cffi" % _module_policy
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep this in sync with python-zstandard.h, rust-ext/src/lib.rs, and debian/changelog.
|
||||||
|
__version__ = "0.25.0"
|
||||||
|
|
||||||
|
_MODE_CLOSED = 0
|
||||||
|
_MODE_READ = 1
|
||||||
|
_MODE_WRITE = 2
|
||||||
|
|
||||||
|
|
||||||
|
def open(
|
||||||
|
filename,
|
||||||
|
mode="rb",
|
||||||
|
cctx=None,
|
||||||
|
dctx=None,
|
||||||
|
encoding=None,
|
||||||
|
errors=None,
|
||||||
|
newline=None,
|
||||||
|
closefd=None,
|
||||||
|
):
|
||||||
|
"""Create a file object with zstd (de)compression.
|
||||||
|
|
||||||
|
The object returned from this function will be a
|
||||||
|
:py:class:`ZstdDecompressionReader` if opened for reading in binary mode,
|
||||||
|
a :py:class:`ZstdCompressionWriter` if opened for writing in binary mode,
|
||||||
|
or an ``io.TextIOWrapper`` if opened for reading or writing in text mode.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
``bytes``, ``str``, or ``os.PathLike`` defining a file to open or a
|
||||||
|
file object (with a ``read()`` or ``write()`` method).
|
||||||
|
:param mode:
|
||||||
|
``str`` File open mode. Accepts any of the open modes recognized by
|
||||||
|
``open()``.
|
||||||
|
:param cctx:
|
||||||
|
``ZstdCompressor`` to use for compression. If not specified and file
|
||||||
|
is opened for writing, the default ``ZstdCompressor`` will be used.
|
||||||
|
:param dctx:
|
||||||
|
``ZstdDecompressor`` to use for decompression. If not specified and file
|
||||||
|
is opened for reading, the default ``ZstdDecompressor`` will be used.
|
||||||
|
:param encoding:
|
||||||
|
``str`` that defines text encoding to use when file is opened in text
|
||||||
|
mode.
|
||||||
|
:param errors:
|
||||||
|
``str`` defining text encoding error handling mode.
|
||||||
|
:param newline:
|
||||||
|
``str`` defining newline to use in text mode.
|
||||||
|
:param closefd:
|
||||||
|
``bool`` whether to close the file when the returned object is closed.
|
||||||
|
Only used if a file object is passed. If a filename is specified, the
|
||||||
|
opened file is always closed when the returned object is closed.
|
||||||
|
"""
|
||||||
|
normalized_mode = mode.replace("t", "")
|
||||||
|
|
||||||
|
if normalized_mode in ("r", "rb"):
|
||||||
|
dctx = dctx or ZstdDecompressor()
|
||||||
|
open_mode = "r"
|
||||||
|
raw_open_mode = "rb"
|
||||||
|
elif normalized_mode in ("w", "wb", "a", "ab", "x", "xb"):
|
||||||
|
cctx = cctx or ZstdCompressor()
|
||||||
|
open_mode = "w"
|
||||||
|
raw_open_mode = normalized_mode
|
||||||
|
if not raw_open_mode.endswith("b"):
|
||||||
|
raw_open_mode = raw_open_mode + "b"
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid mode: {!r}".format(mode))
|
||||||
|
|
||||||
|
if hasattr(os, "PathLike"):
|
||||||
|
types = (str, bytes, os.PathLike)
|
||||||
|
else:
|
||||||
|
types = (str, bytes)
|
||||||
|
|
||||||
|
if isinstance(filename, types): # type: ignore
|
||||||
|
inner_fh = builtins.open(filename, raw_open_mode)
|
||||||
|
closefd = True
|
||||||
|
elif hasattr(filename, "read") or hasattr(filename, "write"):
|
||||||
|
inner_fh = filename
|
||||||
|
closefd = bool(closefd)
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
"filename must be a str, bytes, file or PathLike object"
|
||||||
|
)
|
||||||
|
|
||||||
|
if open_mode == "r":
|
||||||
|
fh = dctx.stream_reader(inner_fh, closefd=closefd)
|
||||||
|
elif open_mode == "w":
|
||||||
|
fh = cctx.stream_writer(inner_fh, closefd=closefd)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("logic error in zstandard.open() handling open mode")
|
||||||
|
|
||||||
|
if "b" not in normalized_mode:
|
||||||
|
return io.TextIOWrapper(
|
||||||
|
fh, encoding=encoding, errors=errors, newline=newline
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return fh
|
||||||
|
|
||||||
|
|
||||||
|
def compress(data: Buffer, level: int = 3) -> bytes:
|
||||||
|
"""Compress source data using the zstd compression format.
|
||||||
|
|
||||||
|
This performs one-shot compression using basic/default compression
|
||||||
|
settings.
|
||||||
|
|
||||||
|
This method is provided for convenience and is equivalent to calling
|
||||||
|
``ZstdCompressor(level=level).compress(data)``.
|
||||||
|
|
||||||
|
If you find yourself calling this function in a tight loop,
|
||||||
|
performance will be greater if you construct a single ``ZstdCompressor``
|
||||||
|
and repeatedly call ``compress()`` on it.
|
||||||
|
"""
|
||||||
|
cctx = ZstdCompressor(level=level)
|
||||||
|
|
||||||
|
return cctx.compress(data)
|
||||||
|
|
||||||
|
|
||||||
|
def decompress(data: Buffer, max_output_size: int = 0) -> bytes:
|
||||||
|
"""Decompress a zstd frame into its original data.
|
||||||
|
|
||||||
|
This performs one-shot decompression using basic/default compression
|
||||||
|
settings.
|
||||||
|
|
||||||
|
This method is provided for convenience and is equivalent to calling
|
||||||
|
``ZstdDecompressor().decompress(data, max_output_size=max_output_size)``.
|
||||||
|
|
||||||
|
If you find yourself calling this function in a tight loop, performance
|
||||||
|
will be greater if you construct a single ``ZstdDecompressor`` and
|
||||||
|
repeatedly call ``decompress()`` on it.
|
||||||
|
"""
|
||||||
|
dctx = ZstdDecompressor()
|
||||||
|
|
||||||
|
return dctx.decompress(data, max_output_size=max_output_size)
|
||||||
+481
@@ -0,0 +1,481 @@
|
|||||||
|
# Copyright (c) 2016-present, Gregory Szorc
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# This software may be modified and distributed under the terms
|
||||||
|
# of the BSD license. See the LICENSE file for details.
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import (
|
||||||
|
IO,
|
||||||
|
BinaryIO,
|
||||||
|
ByteString,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
|
FLUSH_BLOCK: int
|
||||||
|
FLUSH_FRAME: int
|
||||||
|
|
||||||
|
COMPRESSOBJ_FLUSH_FINISH: int
|
||||||
|
COMPRESSOBJ_FLUSH_BLOCK: int
|
||||||
|
|
||||||
|
CONTENTSIZE_UNKNOWN: int
|
||||||
|
CONTENTSIZE_ERROR: int
|
||||||
|
|
||||||
|
MAX_COMPRESSION_LEVEL: int
|
||||||
|
|
||||||
|
COMPRESSION_RECOMMENDED_INPUT_SIZE: int
|
||||||
|
COMPRESSION_RECOMMENDED_OUTPUT_SIZE: int
|
||||||
|
|
||||||
|
DECOMPRESSION_RECOMMENDED_INPUT_SIZE: int
|
||||||
|
DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE: int
|
||||||
|
|
||||||
|
BLOCKSIZELOG_MAX: int
|
||||||
|
BLOCKSIZE_MAX: int
|
||||||
|
|
||||||
|
WINDOWLOG_MIN: int
|
||||||
|
WINDOWLOG_MAX: int
|
||||||
|
|
||||||
|
CHAINLOG_MIN: int
|
||||||
|
CHAINLOG_MAX: int
|
||||||
|
HASHLOG_MIN: int
|
||||||
|
HASHLOG_MAX: int
|
||||||
|
MINMATCH_MIN: int
|
||||||
|
MINMATCH_MAX: int
|
||||||
|
SEARCHLOG_MIN: int
|
||||||
|
SEARCHLOG_MAX: int
|
||||||
|
SEARCHLENGTH_MIN: int
|
||||||
|
SEARCHLENGTH_MAX: int
|
||||||
|
TARGETLENGTH_MIN: int
|
||||||
|
TARGETLENGTH_MAX: int
|
||||||
|
LDM_MINMATCH_MIN: int
|
||||||
|
LDM_MINMATCH_MAX: int
|
||||||
|
LDM_BUCKETSIZELOG_MAX: int
|
||||||
|
|
||||||
|
STRATEGY_FAST: int
|
||||||
|
STRATEGY_DFAST: int
|
||||||
|
STRATEGY_GREEDY: int
|
||||||
|
STRATEGY_LAZY: int
|
||||||
|
STRATEGY_LAZY2: int
|
||||||
|
STRATEGY_BTLAZY2: int
|
||||||
|
STRATEGY_BTOPT: int
|
||||||
|
STRATEGY_BTULTRA: int
|
||||||
|
STRATEGY_BTULTRA2: int
|
||||||
|
|
||||||
|
DICT_TYPE_AUTO: int
|
||||||
|
DICT_TYPE_RAWCONTENT: int
|
||||||
|
DICT_TYPE_FULLDICT: int
|
||||||
|
|
||||||
|
FORMAT_ZSTD1: int
|
||||||
|
FORMAT_ZSTD1_MAGICLESS: int
|
||||||
|
|
||||||
|
ZSTD_VERSION: Tuple[int, int, int]
|
||||||
|
FRAME_HEADER: bytes
|
||||||
|
MAGIC_NUMBER: int
|
||||||
|
|
||||||
|
backend: str
|
||||||
|
backend_features: Set[str]
|
||||||
|
__version__: str
|
||||||
|
|
||||||
|
class ZstdError(Exception): ...
|
||||||
|
|
||||||
|
class BufferSegment(object):
|
||||||
|
offset: int
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
def tobytes(self) -> bytes: ...
|
||||||
|
|
||||||
|
class BufferSegments(object):
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
def __getitem__(self, i: int) -> BufferSegment: ...
|
||||||
|
|
||||||
|
class BufferWithSegments(object):
|
||||||
|
size: int
|
||||||
|
def __init__(self, data: ByteString, segments: ByteString): ...
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
def __getitem__(self, i: int) -> BufferSegment: ...
|
||||||
|
def segments(self): ...
|
||||||
|
def tobytes(self) -> bytes: ...
|
||||||
|
|
||||||
|
class BufferWithSegmentsCollection(object):
|
||||||
|
def __init__(self, *args): ...
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
def __getitem__(self, i: int) -> BufferSegment: ...
|
||||||
|
def size(self) -> int: ...
|
||||||
|
|
||||||
|
class ZstdCompressionParameters(object):
|
||||||
|
@staticmethod
|
||||||
|
def from_level(
|
||||||
|
level: int, source_size: int = ..., dict_size: int = ..., **kwargs
|
||||||
|
) -> "ZstdCompressionParameters": ...
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
format: int = ...,
|
||||||
|
compression_level: int = ...,
|
||||||
|
window_log: int = ...,
|
||||||
|
hash_log: int = ...,
|
||||||
|
chain_log: int = ...,
|
||||||
|
search_log: int = ...,
|
||||||
|
min_match: int = ...,
|
||||||
|
target_length: int = ...,
|
||||||
|
strategy: int = ...,
|
||||||
|
write_content_size: int = ...,
|
||||||
|
write_checksum: int = ...,
|
||||||
|
write_dict_id: int = ...,
|
||||||
|
job_size: int = ...,
|
||||||
|
overlap_log: int = ...,
|
||||||
|
force_max_window: int = ...,
|
||||||
|
enable_ldm: int = ...,
|
||||||
|
ldm_hash_log: int = ...,
|
||||||
|
ldm_min_match: int = ...,
|
||||||
|
ldm_bucket_size_log: int = ...,
|
||||||
|
ldm_hash_rate_log: int = ...,
|
||||||
|
threads: int = ...,
|
||||||
|
): ...
|
||||||
|
@property
|
||||||
|
def format(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def compression_level(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def window_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def hash_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def chain_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def search_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def min_match(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def target_length(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def strategy(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def write_content_size(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def write_checksum(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def write_dict_id(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def job_size(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def overlap_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def force_max_window(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def enable_ldm(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def ldm_hash_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def ldm_min_match(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def ldm_bucket_size_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def ldm_hash_rate_log(self) -> int: ...
|
||||||
|
@property
|
||||||
|
def threads(self) -> int: ...
|
||||||
|
def estimated_compression_context_size(self) -> int: ...
|
||||||
|
|
||||||
|
class CompressionParameters(ZstdCompressionParameters): ...
|
||||||
|
|
||||||
|
class ZstdCompressionDict(object):
|
||||||
|
k: int
|
||||||
|
d: int
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
data: ByteString,
|
||||||
|
dict_type: int = ...,
|
||||||
|
k: int = ...,
|
||||||
|
d: int = ...,
|
||||||
|
): ...
|
||||||
|
def __len__(self) -> int: ...
|
||||||
|
def dict_id(self) -> int: ...
|
||||||
|
def as_bytes(self) -> bytes: ...
|
||||||
|
def precompute_compress(
|
||||||
|
self,
|
||||||
|
level: int = ...,
|
||||||
|
compression_params: ZstdCompressionParameters = ...,
|
||||||
|
): ...
|
||||||
|
|
||||||
|
class ZstdCompressionObj(object):
|
||||||
|
def compress(self, data: ByteString) -> bytes: ...
|
||||||
|
def flush(self, flush_mode: int = ...) -> bytes: ...
|
||||||
|
|
||||||
|
class ZstdCompressionChunker(object):
|
||||||
|
def compress(self, data: ByteString): ...
|
||||||
|
def flush(self): ...
|
||||||
|
def finish(self): ...
|
||||||
|
|
||||||
|
class ZstdCompressionReader(BinaryIO):
|
||||||
|
def __enter__(self) -> "ZstdCompressionReader": ...
|
||||||
|
def __exit__(self, exc_type, exc_value, exc_tb): ...
|
||||||
|
def readable(self) -> bool: ...
|
||||||
|
def writable(self) -> bool: ...
|
||||||
|
def seekable(self) -> bool: ...
|
||||||
|
def readline(self, limit: int = ...) -> bytes: ...
|
||||||
|
def readlines(self, hint: int = ...) -> List[bytes]: ...
|
||||||
|
def write(self, data: ByteString): ...
|
||||||
|
def writelines(self, data: Iterable[bytes]): ...
|
||||||
|
def isatty(self) -> bool: ...
|
||||||
|
def flush(self): ...
|
||||||
|
def close(self): ...
|
||||||
|
@property
|
||||||
|
def closed(self) -> bool: ...
|
||||||
|
def tell(self) -> int: ...
|
||||||
|
def readall(self) -> bytes: ...
|
||||||
|
def __iter__(self): ...
|
||||||
|
def __next__(self): ...
|
||||||
|
def next(self): ...
|
||||||
|
def read(self, size: int = ...) -> bytes: ...
|
||||||
|
def read1(self, size: int = ...) -> bytes: ...
|
||||||
|
def readinto(self, b) -> int: ...
|
||||||
|
def readinto1(self, b) -> int: ...
|
||||||
|
|
||||||
|
class ZstdCompressionWriter(BinaryIO):
|
||||||
|
def __enter__(self) -> "ZstdCompressionWriter": ...
|
||||||
|
def __exit__(self, exc_type, exc_value, exc_tb): ...
|
||||||
|
def memory_size(self) -> int: ...
|
||||||
|
def fileno(self) -> int: ...
|
||||||
|
def close(self): ...
|
||||||
|
@property
|
||||||
|
def closed(self) -> bool: ...
|
||||||
|
def isatty(self) -> bool: ...
|
||||||
|
def readable(self) -> bool: ...
|
||||||
|
def readline(self, size: int = ...) -> bytes: ...
|
||||||
|
def readlines(self, hint: int = ...) -> List[bytes]: ...
|
||||||
|
def seek(self, offset: int, whence: int = ...): ...
|
||||||
|
def seekable(self) -> bool: ...
|
||||||
|
def truncate(self, size: int = ...): ...
|
||||||
|
def writable(self) -> bool: ...
|
||||||
|
def writelines(self, lines: Iterable[bytes]): ...
|
||||||
|
def read(self, size: int = ...) -> bytes: ...
|
||||||
|
def readall(self) -> bytes: ...
|
||||||
|
def readinto(self, b): ...
|
||||||
|
def write(self, data: ByteString) -> int: ...
|
||||||
|
def flush(self, flush_mode: int = ...) -> int: ...
|
||||||
|
def tell(self) -> int: ...
|
||||||
|
|
||||||
|
class ZstdCompressor(object):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
level: int = ...,
|
||||||
|
dict_data: Optional[ZstdCompressionDict] = ...,
|
||||||
|
compression_params: Optional[ZstdCompressionParameters] = ...,
|
||||||
|
write_checksum: Optional[bool] = ...,
|
||||||
|
write_content_size: Optional[bool] = ...,
|
||||||
|
write_dict_id: Optional[bool] = ...,
|
||||||
|
threads: int = ...,
|
||||||
|
): ...
|
||||||
|
def memory_size(self) -> int: ...
|
||||||
|
def compress(self, data: ByteString) -> bytes: ...
|
||||||
|
def compressobj(self, size: int = ...) -> ZstdCompressionObj: ...
|
||||||
|
def chunker(
|
||||||
|
self, size: int = ..., chunk_size: int = ...
|
||||||
|
) -> ZstdCompressionChunker: ...
|
||||||
|
def copy_stream(
|
||||||
|
self,
|
||||||
|
ifh: IO[bytes],
|
||||||
|
ofh: IO[bytes],
|
||||||
|
size: int = ...,
|
||||||
|
read_size: int = ...,
|
||||||
|
write_size: int = ...,
|
||||||
|
) -> Tuple[int, int]: ...
|
||||||
|
def stream_reader(
|
||||||
|
self,
|
||||||
|
source: Union[IO[bytes], ByteString],
|
||||||
|
size: int = ...,
|
||||||
|
read_size: int = ...,
|
||||||
|
*,
|
||||||
|
closefd: bool = ...,
|
||||||
|
) -> ZstdCompressionReader: ...
|
||||||
|
def stream_writer(
|
||||||
|
self,
|
||||||
|
writer: IO[bytes],
|
||||||
|
size: int = ...,
|
||||||
|
write_size: int = ...,
|
||||||
|
write_return_read: bool = ...,
|
||||||
|
*,
|
||||||
|
closefd: bool = ...,
|
||||||
|
) -> ZstdCompressionWriter: ...
|
||||||
|
def read_to_iter(
|
||||||
|
self,
|
||||||
|
reader: Union[IO[bytes], ByteString],
|
||||||
|
size: int = ...,
|
||||||
|
read_size: int = ...,
|
||||||
|
write_size: int = ...,
|
||||||
|
) -> Generator[bytes, None, None]: ...
|
||||||
|
def frame_progression(self) -> Tuple[int, int, int]: ...
|
||||||
|
def multi_compress_to_buffer(
|
||||||
|
self,
|
||||||
|
data: Union[
|
||||||
|
BufferWithSegments,
|
||||||
|
BufferWithSegmentsCollection,
|
||||||
|
List[ByteString],
|
||||||
|
],
|
||||||
|
threads: int = ...,
|
||||||
|
) -> BufferWithSegmentsCollection: ...
|
||||||
|
|
||||||
|
class ZstdDecompressionObj(object):
|
||||||
|
def decompress(self, data: ByteString) -> bytes: ...
|
||||||
|
def flush(self, length: int = ...) -> bytes: ...
|
||||||
|
@property
|
||||||
|
def unused_data(self) -> bytes: ...
|
||||||
|
@property
|
||||||
|
def unconsumed_tail(self) -> bytes: ...
|
||||||
|
@property
|
||||||
|
def eof(self) -> bool: ...
|
||||||
|
|
||||||
|
class ZstdDecompressionReader(BinaryIO):
|
||||||
|
def __enter__(self) -> "ZstdDecompressionReader": ...
|
||||||
|
def __exit__(self, exc_type, exc_value, exc_tb): ...
|
||||||
|
def readable(self) -> bool: ...
|
||||||
|
def writable(self) -> bool: ...
|
||||||
|
def seekable(self) -> bool: ...
|
||||||
|
def readline(self, size: int = ...): ...
|
||||||
|
def readlines(self, hint: int = ...): ...
|
||||||
|
def write(self, data: ByteString): ...
|
||||||
|
def writelines(self, lines: Iterable[bytes]): ...
|
||||||
|
def isatty(self) -> bool: ...
|
||||||
|
def flush(self): ...
|
||||||
|
def close(self): ...
|
||||||
|
@property
|
||||||
|
def closed(self) -> bool: ...
|
||||||
|
def tell(self) -> int: ...
|
||||||
|
def readall(self) -> bytes: ...
|
||||||
|
def __iter__(self): ...
|
||||||
|
def __next__(self): ...
|
||||||
|
def next(self): ...
|
||||||
|
def read(self, size: int = ...) -> bytes: ...
|
||||||
|
def readinto(self, b) -> int: ...
|
||||||
|
def read1(self, size: int = ...) -> bytes: ...
|
||||||
|
def readinto1(self, b) -> int: ...
|
||||||
|
def seek(self, pos: int, whence: int = ...) -> int: ...
|
||||||
|
|
||||||
|
class ZstdDecompressionWriter(BinaryIO):
|
||||||
|
def __enter__(self) -> "ZstdDecompressionWriter": ...
|
||||||
|
def __exit__(self, exc_type, exc_value, exc_tb): ...
|
||||||
|
def memory_size(self) -> int: ...
|
||||||
|
def close(self): ...
|
||||||
|
@property
|
||||||
|
def closed(self) -> bool: ...
|
||||||
|
def fileno(self) -> int: ...
|
||||||
|
def flush(self): ...
|
||||||
|
def isatty(self) -> bool: ...
|
||||||
|
def readable(self) -> bool: ...
|
||||||
|
def readline(self, size: int = ...): ...
|
||||||
|
def readlines(self, hint: int = ...): ...
|
||||||
|
def seek(self, offset: int, whence: int = ...): ...
|
||||||
|
def seekable(self) -> bool: ...
|
||||||
|
def tell(self): ...
|
||||||
|
def truncate(self, size: int = ...): ...
|
||||||
|
def writable(self) -> bool: ...
|
||||||
|
def writelines(self, lines: Iterable[bytes]): ...
|
||||||
|
def read(self, size: int = ...): ...
|
||||||
|
def readall(self): ...
|
||||||
|
def readinto(self, b): ...
|
||||||
|
def write(self, data: ByteString) -> int: ...
|
||||||
|
|
||||||
|
class ZstdDecompressor(object):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
dict_data: Optional[ZstdCompressionDict] = ...,
|
||||||
|
max_window_size: int = ...,
|
||||||
|
format: int = ...,
|
||||||
|
): ...
|
||||||
|
def memory_size(self) -> int: ...
|
||||||
|
def decompress(
|
||||||
|
self,
|
||||||
|
data: ByteString,
|
||||||
|
max_output_size: int = ...,
|
||||||
|
read_across_frames: bool = ...,
|
||||||
|
allow_extra_data: bool = ...,
|
||||||
|
) -> bytes: ...
|
||||||
|
def stream_reader(
|
||||||
|
self,
|
||||||
|
source: Union[IO[bytes], ByteString],
|
||||||
|
read_size: int = ...,
|
||||||
|
read_across_frames: bool = ...,
|
||||||
|
*,
|
||||||
|
closefd=False,
|
||||||
|
) -> ZstdDecompressionReader: ...
|
||||||
|
def decompressobj(
|
||||||
|
self, write_size: int = ..., read_across_frames: bool = False
|
||||||
|
) -> ZstdDecompressionObj: ...
|
||||||
|
def read_to_iter(
|
||||||
|
self,
|
||||||
|
reader: Union[IO[bytes], ByteString],
|
||||||
|
read_size: int = ...,
|
||||||
|
write_size: int = ...,
|
||||||
|
skip_bytes: int = ...,
|
||||||
|
) -> Generator[bytes, None, None]: ...
|
||||||
|
def stream_writer(
|
||||||
|
self,
|
||||||
|
writer: IO[bytes],
|
||||||
|
write_size: int = ...,
|
||||||
|
write_return_read: bool = ...,
|
||||||
|
*,
|
||||||
|
closefd: bool = ...,
|
||||||
|
) -> ZstdDecompressionWriter: ...
|
||||||
|
def copy_stream(
|
||||||
|
self,
|
||||||
|
ifh: IO[bytes],
|
||||||
|
ofh: IO[bytes],
|
||||||
|
read_size: int = ...,
|
||||||
|
write_size: int = ...,
|
||||||
|
) -> Tuple[int, int]: ...
|
||||||
|
def decompress_content_dict_chain(
|
||||||
|
self, frames: list[ByteString]
|
||||||
|
) -> bytes: ...
|
||||||
|
def multi_decompress_to_buffer(
|
||||||
|
self,
|
||||||
|
frames: Union[
|
||||||
|
BufferWithSegments,
|
||||||
|
BufferWithSegmentsCollection,
|
||||||
|
List[ByteString],
|
||||||
|
],
|
||||||
|
decompressed_sizes: ByteString = ...,
|
||||||
|
threads: int = ...,
|
||||||
|
) -> BufferWithSegmentsCollection: ...
|
||||||
|
|
||||||
|
class FrameParameters(object):
|
||||||
|
content_size: int
|
||||||
|
window_size: int
|
||||||
|
dict_id: int
|
||||||
|
has_checksum: bool
|
||||||
|
|
||||||
|
def estimate_decompression_context_size() -> int: ...
|
||||||
|
def frame_content_size(data: ByteString) -> int: ...
|
||||||
|
def frame_header_size(data: ByteString) -> int: ...
|
||||||
|
def get_frame_parameters(
|
||||||
|
data: ByteString, format: Optional[int] = None
|
||||||
|
) -> FrameParameters: ...
|
||||||
|
def train_dictionary(
|
||||||
|
dict_size: int,
|
||||||
|
samples: list[ByteString],
|
||||||
|
k: int = ...,
|
||||||
|
d: int = ...,
|
||||||
|
f: int = ...,
|
||||||
|
split_point: float = ...,
|
||||||
|
accel: int = ...,
|
||||||
|
notifications: int = ...,
|
||||||
|
dict_id: int = ...,
|
||||||
|
level: int = ...,
|
||||||
|
steps: int = ...,
|
||||||
|
threads: int = ...,
|
||||||
|
) -> ZstdCompressionDict: ...
|
||||||
|
def open(
|
||||||
|
filename: Union[bytes, str, os.PathLike, BinaryIO],
|
||||||
|
mode: str = ...,
|
||||||
|
cctx: Optional[ZstdCompressor] = ...,
|
||||||
|
dctx: Optional[ZstdDecompressor] = ...,
|
||||||
|
encoding: Optional[str] = ...,
|
||||||
|
errors: Optional[str] = ...,
|
||||||
|
newline: Optional[str] = ...,
|
||||||
|
closefd: bool = ...,
|
||||||
|
): ...
|
||||||
|
def compress(data: ByteString, level: int = ...) -> bytes: ...
|
||||||
|
def decompress(data: ByteString, max_output_size: int = ...) -> bytes: ...
|
||||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
+4478
File diff suppressed because it is too large
Load Diff
@@ -21,15 +21,15 @@ export const CODEX_LINK_PORT = 8795
|
|||||||
export const CODEX_LINK_ORIGIN = `http://127.0.0.1:${CODEX_LINK_PORT}`
|
export const CODEX_LINK_ORIGIN = `http://127.0.0.1:${CODEX_LINK_PORT}`
|
||||||
export const CODEX_LINK_STATUS_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama/status`
|
export const CODEX_LINK_STATUS_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama/status`
|
||||||
export const CODEX_LINK_STOP_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama/stop`
|
export const CODEX_LINK_STOP_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama/stop`
|
||||||
|
export const CODEX_LINK_REFRESH_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama/refresh`
|
||||||
|
|
||||||
/** The trailing comment that marks the line as ours. */
|
/** The trailing comment that marks the line as ours. */
|
||||||
export const CODEX_LINK_MARKER = '# AITURK IDE 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
|
// 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.
|
// another version is restarted so an updated IDE never keeps an old bridge alive.
|
||||||
export const CODEX_BRIDGE_VERSION = '1'
|
export const CODEX_BRIDGE_VERSION = '5'
|
||||||
export const CODEX_LINK_LINE = `openai_base_url = "${CODEX_LINK_ORIGIN}/backend-api/codex" ${CODEX_LINK_MARKER}`
|
export const CODEX_LINK_LINE = `openai_base_url = "${CODEX_LINK_ORIGIN}/backend-api/codex" ${CODEX_LINK_MARKER}`
|
||||||
|
|
||||||
export const CODEX_LINK_BACKUP_SUFFIX = '.aiturk-yedek'
|
|
||||||
export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
||||||
export const CODEX_LINK_RUN_VALUE = 'AITURK-IDE-Codex-Baglama'
|
export const CODEX_LINK_RUN_VALUE = 'AITURK-IDE-Codex-Baglama'
|
||||||
export const CODEX_LINK_LAUNCHER_NAME = 'baslat.pyw'
|
export const CODEX_LINK_LAUNCHER_NAME = 'baslat.pyw'
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { describe, expect, it } from 'vitest'
|
|||||||
|
|
||||||
import { CODEX_BRIDGE_START_BUDGET_MS, type CodexLinkDeps, type CodexLinkPaths, createCodexLink } from './codex-link'
|
import { CODEX_BRIDGE_START_BUDGET_MS, type CodexLinkDeps, type CodexLinkPaths, createCodexLink } from './codex-link'
|
||||||
import {
|
import {
|
||||||
|
CODEX_BRIDGE_VERSION,
|
||||||
CODEX_LINK_LINE,
|
CODEX_LINK_LINE,
|
||||||
|
CODEX_LINK_REFRESH_URL,
|
||||||
CODEX_LINK_STATUS_URL,
|
CODEX_LINK_STATUS_URL,
|
||||||
CODEX_LINK_STOP_URL,
|
CODEX_LINK_STOP_URL,
|
||||||
codexBridgeArgs,
|
codexBridgeArgs,
|
||||||
@@ -20,8 +22,7 @@ const PATHS: CodexLinkPaths = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BRIDGE_JSON = `${PATHS.bridgeHome}\\bridge.json`
|
const BRIDGE_JSON = `${PATHS.bridgeHome}\\bridge.json`
|
||||||
const BACKUP = `${PATHS.codexConfig}.aiturk-yedek`
|
const STATUS_OK = { ok: true, surum: CODEX_BRIDGE_VERSION, ide_durum: 'tamam', ide_model: 7, ozet: true }
|
||||||
const STATUS_OK = { ok: true, surum: '1', ide_durum: 'tamam', ide_model: 7, ozet: true }
|
|
||||||
const USER_CONFIG = 'model = "gpt-6"\r\n\r\n[features]\r\nweb = true\r\n'
|
const USER_CONFIG = 'model = "gpt-6"\r\n\r\n[features]\r\nweb = true\r\n'
|
||||||
|
|
||||||
interface World {
|
interface World {
|
||||||
@@ -72,12 +73,6 @@ function createWorld(patch: Partial<World> & { files?: Map<string, string> } = {
|
|||||||
world.events.push(`write ${file}`)
|
world.events.push(`write ${file}`)
|
||||||
world.files.set(file, text)
|
world.files.set(file, text)
|
||||||
},
|
},
|
||||||
backupOnce: (source, target) => {
|
|
||||||
if (!world.files.has(target)) {
|
|
||||||
world.events.push(`backup ${target}`)
|
|
||||||
world.files.set(target, world.files.get(source) ?? '')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
spawnBridge: (command, args, env) => {
|
spawnBridge: (command, args, env) => {
|
||||||
world.events.push(`spawn ${command} ${JSON.stringify(args)} ${JSON.stringify(env)}`)
|
world.events.push(`spawn ${command} ${JSON.stringify(args)} ${JSON.stringify(env)}`)
|
||||||
spawned = true
|
spawned = true
|
||||||
@@ -103,6 +98,12 @@ function createWorld(patch: Partial<World> & { files?: Map<string, string> } = {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (method === 'POST' && url === CODEX_LINK_REFRESH_URL) {
|
||||||
|
world.events.push('refresh')
|
||||||
|
|
||||||
|
return world.listening ? STATUS_OK : null
|
||||||
|
}
|
||||||
|
|
||||||
if (method === 'POST' && url === CODEX_LINK_STOP_URL) {
|
if (method === 'POST' && url === CODEX_LINK_STOP_URL) {
|
||||||
world.events.push('stop')
|
world.events.push('stop')
|
||||||
|
|
||||||
@@ -205,19 +206,17 @@ describe('applyCodexLink', () => {
|
|||||||
hermesHome: PATHS.hermesHome
|
hermesHome: PATHS.hermesHome
|
||||||
})
|
})
|
||||||
|
|
||||||
// bridge.json → start → verified answer → config.toml (backed up) → login item
|
// bridge.json → start → verified answer → config.toml → login item
|
||||||
expect(world.events).toEqual([
|
expect(world.events).toEqual([
|
||||||
`write ${BRIDGE_JSON}`,
|
`write ${BRIDGE_JSON}`,
|
||||||
`spawn ${PATHS.pythonw} ${JSON.stringify(args)} ${JSON.stringify(env)}`,
|
`spawn ${PATHS.pythonw} ${JSON.stringify(args)} ${JSON.stringify(env)}`,
|
||||||
'status ok',
|
'status ok',
|
||||||
`backup ${BACKUP}`,
|
|
||||||
`write ${PATHS.codexConfig}`,
|
`write ${PATHS.codexConfig}`,
|
||||||
'autostart set'
|
'autostart set'
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(JSON.parse(world.files.get(BRIDGE_JSON) ?? '')).toEqual(codexBridgeConfig())
|
expect(JSON.parse(world.files.get(BRIDGE_JSON) ?? '')).toEqual(codexBridgeConfig())
|
||||||
expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`)
|
expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`)
|
||||||
expect(world.files.get(BACKUP)).toBe(USER_CONFIG)
|
|
||||||
expect(world.autostart).toBe(
|
expect(world.autostart).toBe(
|
||||||
`"${PATHS.pythonw}" "${PATHS.bridgeScript}" --home "${PATHS.bridgeHome}" --hermes-home "${PATHS.hermesHome}"`
|
`"${PATHS.pythonw}" "${PATHS.bridgeScript}" --home "${PATHS.bridgeHome}" --hermes-home "${PATHS.hermesHome}"`
|
||||||
)
|
)
|
||||||
@@ -231,7 +230,6 @@ describe('applyCodexLink', () => {
|
|||||||
expect(status).toMatchObject({ enabled: true, bridgeRunning: false, lastError: 'bridge-start-failed' })
|
expect(status).toMatchObject({ enabled: true, bridgeRunning: false, lastError: 'bridge-start-failed' })
|
||||||
expect(spawns(world)).toHaveLength(1)
|
expect(spawns(world)).toHaveLength(1)
|
||||||
expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG)
|
expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG)
|
||||||
expect(world.files.has(BACKUP)).toBe(false)
|
|
||||||
expect(world.autostart).toBeNull()
|
expect(world.autostart).toBeNull()
|
||||||
expect(writes(world)).toEqual([`write ${BRIDGE_JSON}`])
|
expect(writes(world)).toEqual([`write ${BRIDGE_JSON}`])
|
||||||
// The start poll is bounded by its one budget.
|
// The start poll is bounded by its one budget.
|
||||||
@@ -273,13 +271,13 @@ describe('applyCodexLink', () => {
|
|||||||
expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`)
|
expect(world.files.get(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('never overwrites the first backup', async () => {
|
it('asks a running bridge to re-read the IDE session on every apply', async () => {
|
||||||
const { link, world } = createWorld({ listening: true })
|
const { link, world } = createWorld({ listening: true })
|
||||||
world.files.set(BACKUP, 'the original original')
|
|
||||||
|
|
||||||
await link.apply()
|
await link.apply({ sync: true })
|
||||||
|
|
||||||
expect(world.files.get(BACKUP)).toBe('the original original')
|
expect(world.events).toContain('refresh')
|
||||||
|
expect(spawns(world)).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('falls back to python.exe when pythonw.exe is missing', async () => {
|
it('falls back to python.exe when pythonw.exe is missing', async () => {
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ import path from 'node:path'
|
|||||||
import {
|
import {
|
||||||
addCodexLinkLine,
|
addCodexLinkLine,
|
||||||
CODEX_BRIDGE_VERSION,
|
CODEX_BRIDGE_VERSION,
|
||||||
CODEX_LINK_BACKUP_SUFFIX,
|
|
||||||
CODEX_LINK_LAUNCHER_NAME,
|
CODEX_LINK_LAUNCHER_NAME,
|
||||||
|
CODEX_LINK_REFRESH_URL,
|
||||||
CODEX_LINK_RUN_KEY,
|
CODEX_LINK_RUN_KEY,
|
||||||
CODEX_LINK_RUN_VALUE,
|
CODEX_LINK_RUN_VALUE,
|
||||||
CODEX_LINK_STATUS_URL,
|
CODEX_LINK_STATUS_URL,
|
||||||
@@ -66,8 +66,6 @@ export interface CodexLinkDeps {
|
|||||||
readText: (file: string) => null | string
|
readText: (file: string) => null | string
|
||||||
/** tmp + rename; creates the parent folder. */
|
/** tmp + rename; creates the parent folder. */
|
||||||
writeTextAtomic: (file: string, text: string) => void
|
writeTextAtomic: (file: string, text: string) => void
|
||||||
/** Copy `source` to `target` unless `target` already exists. */
|
|
||||||
backupOnce: (source: string, target: string) => void
|
|
||||||
/** Start the bridge detached and hidden; it must outlive the IDE. */
|
/** Start the bridge detached and hidden; it must outlive the IDE. */
|
||||||
spawnBridge: (command: string, args: string[], env: Record<string, string>) => void
|
spawnBridge: (command: string, args: string[], env: Record<string, string>) => void
|
||||||
/** Parsed JSON body, or null when nothing answered within `budgetMs`. */
|
/** Parsed JSON body, or null when nothing answered within `budgetMs`. */
|
||||||
@@ -306,7 +304,17 @@ export function createCodexLink(deps: CodexLinkDeps): CodexLink {
|
|||||||
bridge = NOT_RUNNING
|
bridge = NOT_RUNNING
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!bridge.running) {
|
if (bridge.running) {
|
||||||
|
// Already up: have it re-read the IDE session and model list now (a sign-in
|
||||||
|
// since its last periodic refresh would otherwise show up minutes later).
|
||||||
|
try {
|
||||||
|
const refreshed = readProbe(await deps.request('POST', CODEX_LINK_REFRESH_URL, CODEX_BRIDGE_START_BUDGET_MS))
|
||||||
|
|
||||||
|
bridge = refreshed.running ? refreshed : bridge
|
||||||
|
} catch {
|
||||||
|
// The status it already gave stays valid.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
bridge = await startBridge(python)
|
bridge = await startBridge(python)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,7 +331,6 @@ export function createCodexLink(deps: CodexLinkDeps): CodexLink {
|
|||||||
|
|
||||||
if (edit.changed) {
|
if (edit.changed) {
|
||||||
try {
|
try {
|
||||||
deps.backupOnce(paths.codexConfig, `${paths.codexConfig}${CODEX_LINK_BACKUP_SUFFIX}`)
|
|
||||||
deps.writeTextAtomic(paths.codexConfig, edit.text)
|
deps.writeTextAtomic(paths.codexConfig, edit.text)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fail('config-write-failed', error)
|
fail('config-write-failed', error)
|
||||||
@@ -461,11 +468,6 @@ export function createNodeCodexLinkDeps(paths: CodexLinkPaths, log: (message: st
|
|||||||
exists: file => fs.existsSync(file),
|
exists: file => fs.existsSync(file),
|
||||||
readText: readTextLossless,
|
readText: readTextLossless,
|
||||||
writeTextAtomic,
|
writeTextAtomic,
|
||||||
backupOnce: (source, target) => {
|
|
||||||
if (!fs.existsSync(target)) {
|
|
||||||
fs.copyFileSync(source, target, fs.constants.COPYFILE_EXCL)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
spawnBridge: (command, args, env) => {
|
spawnBridge: (command, args, env) => {
|
||||||
fs.mkdirSync(paths.bridgeHome, { recursive: true })
|
fs.mkdirSync(paths.bridgeHome, { recursive: true })
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "aiturk-ide",
|
"name": "aiturk-ide",
|
||||||
"productName": "AITURK IDE",
|
"productName": "AITURK IDE",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0-beta.21",
|
"version": "1.0.0-beta.24",
|
||||||
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
|
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
|
||||||
"author": "AITURK / TurkServis",
|
"author": "AITURK / TurkServis",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -304,7 +304,9 @@
|
|||||||
"from": "assets/codex-bridge",
|
"from": "assets/codex-bridge",
|
||||||
"to": "codex-bridge",
|
"to": "codex-bridge",
|
||||||
"filter": [
|
"filter": [
|
||||||
"*.py",
|
"**/*.py",
|
||||||
|
"**/*.pyd",
|
||||||
|
"**/LICENSE",
|
||||||
"!test_*.py"
|
"!test_*.py"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+1
-1
@@ -65,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
"apps/desktop": {
|
"apps/desktop": {
|
||||||
"name": "aiturk-ide",
|
"name": "aiturk-ide",
|
||||||
"version": "1.0.0-beta.21",
|
"version": "1.0.0-beta.24",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@assistant-ui/core": "0.2.23",
|
"@assistant-ui/core": "0.2.23",
|
||||||
|
|||||||
Reference in New Issue
Block a user