diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 299d2b0..d656935 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - name: Codex bridge regression tests run: '"$RUNNER_TEMP/ci-venv/bin/python" -m unittest discover -s apps/desktop/assets/codex-bridge' - name: Native watch and speech mux pytest regressions - run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest apps/desktop/assets/codex-bridge/test_bekci_watch.py apps/desktop/assets/codex-bridge/test_native_speech_mux.py -q -p no:cacheprovider -rf' + run: '"$RUNNER_TEMP/ci-venv/bin/python" -m pytest apps/desktop/assets/codex-bridge/test_bekci_watch.py apps/desktop/assets/codex-bridge/test_native_speech_mux.py apps/desktop/assets/codex-bridge/test_live_limits.py -q -p no:cacheprovider -rf' - name: Mobile and desktop startup regression tests run: | npm install --prefix "$RUNNER_TEMP/mobile-tests" --no-save --ignore-scripts --no-audit --no-fund --legacy-peer-deps vitest@4.1.10 ws@8.21.1 diff --git a/README.AITURK.md b/README.AITURK.md index 070c470..e5b1e30 100644 --- a/README.AITURK.md +++ b/README.AITURK.md @@ -25,6 +25,8 @@ ayrıntı seviyesi, korunacak mesajlaşmalar, yüzde/token bütçesi ve otomatik seçimini sunar. Varsayılanlar: Gemini 3.8 Flash High, yedek Gemini 3 Flash, 10 mesajlaşma, geçişte %70, otomatik tetiklemede %70 ve sonrasında %50. Kapasite bağlantıdan gelen model bilgisiyle hesaplanır; 272K genel tavan yoktur. +`/models` içindeki doğrulanmış sayısal bağlam bilgisi, model adından çıkarılan +yerel varsayılandan önce gelir; gözlenmiş daha düşük bir gerçek sınır korunur. Girdi hesabı talimatlar ve araçları da içeren temkinli bir token tahminidir. Abonelik modellerinin kendi aralarındaki istekler ve GPT-Live ses yolu özgün @@ -58,8 +60,9 @@ npm run builder --workspace=apps/desktop -- --win nsis --x64 --publish never Dağıtılacak derleme temiz, yayımlanmış Git commit'inden üretilmelidir. İlk açılış ajan kurulumunu paketteki `install-stamp.json` commit'ine sabitler. Windows -imzası ve temiz kurulum doğrulanmadan bir paket doğrulanmış üretim sürümü diye -sunulmamalıdır. macOS ve Linux paketleri bu beta kapsamında doğrulanmamıştır. +paketleri imzasız EXE/ZIP olarak yayımlanır; SHA-256 ve gerçek Windows çalıştırma +kontrolü gereklidir. Kullanıcı açıkça istemeden imzalama yapılmaz. macOS ve Linux +paketleri bu beta kapsamında doğrulanmamıştır. ## Lisans ve kaynak diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index 88e6793..7d8e116 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -34,7 +34,7 @@ import urllib.request import gecis_context HERE = os.path.dirname(os.path.abspath(__file__)) -SURUM = "14" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION) +SURUM = "15" # 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] diff --git a/apps/desktop/assets/codex-bridge/gecis_context.py b/apps/desktop/assets/codex-bridge/gecis_context.py index c5eed2f..a6abcbd 100644 --- a/apps/desktop/assets/codex-bridge/gecis_context.py +++ b/apps/desktop/assets/codex-bridge/gecis_context.py @@ -104,6 +104,18 @@ def portable_reasoning(req): return out result = [] for item in items: + if isinstance(item, dict) and item.get("type") == "agent_message": + # Peer messages carry delegated tasks/results. External Responses + # translators do not understand this Codex-specific item type. + # Keep provenance as data, never as system/developer authority. + content = item.get("content") or [] + if isinstance(content, str): + content = [{"type": "input_text", "text": content}] + result.append({"type": "message", "role": "user", "content": [ + {"type": "input_text", "text": "Agent message: " + encoded({ + "author": item.get("author"), "recipient": item.get("recipient")})} + ] + content}) + continue if not isinstance(item, dict) or item.get("type") != "reasoning": result.append(item) continue diff --git a/apps/desktop/assets/codex-bridge/ide_catalog.py b/apps/desktop/assets/codex-bridge/ide_catalog.py index ea5796f..14bb92c 100644 --- a/apps/desktop/assets/codex-bridge/ide_catalog.py +++ b/apps/desktop/assets/codex-bridge/ide_catalog.py @@ -348,11 +348,13 @@ def yukle(ua="AstraBridge/1.0 (codex)", timeout=30, home=None, ag=True): if not girdiler: raise ValueError("Gecitte kullanilabilir OmniRoute sohbet modeli yok") kayitlar = {m["id"]: m for m in metadata if m.get("id")} + ilanlar = {m["id"]: m for m in data if m.get("id")} nano = pencere.nano_yukle(home, ua, ag=ag) # ag=False: acilista yalniz onbellek ogr = pencere.ogrenilen(home) aracsiz = pencere.aracsizlar(home) for g in girdiler: - bilgi = pencere.coz(g["id"], g["kind"], g.get("yol"), kayitlar.get(g["id"]), nano, ogr, aracsiz) + bilgi = pencere.coz(g["id"], g["kind"], g.get("yol"), kayitlar.get(g["id"]), nano, ogr, aracsiz, + ilan=ilanlar.get(g["id"])) g["pencere"], g["gorsel"], g["arac"], g["pencere_kaynak"] = (bilgi["pencere"], bilgi["gorsel"], bilgi["arac"], bilgi["kaynak"]) g["claude"] = pencere.claude_var(g["kind"], g.get("yol"), kayitlar.get(g["id"])) diff --git a/apps/desktop/assets/codex-bridge/kopru.py b/apps/desktop/assets/codex-bridge/kopru.py index 816068d..d4a442b 100644 --- a/apps/desktop/assets/codex-bridge/kopru.py +++ b/apps/desktop/assets/codex-bridge/kopru.py @@ -40,7 +40,7 @@ from bekci_watch import MetadataFilter # Installed only by owned native launcher, never an HTTP control endpoint. WATCH_OWNER = None -SURUM = "14" +SURUM = "15" HERE = os.path.dirname(os.path.abspath(__file__)) try: with open(os.path.abspath(__file__), "rb") as _f: @@ -365,6 +365,10 @@ def arac_semalarini_duzelt(req): def flatten_request(req, tool_names=None): """additional_tools/namespace/custom -> duz function tools. Donus: custom arac adlari kumesi.""" + if isinstance(req.get("input"), list) and any( + isinstance(item, dict) and item.get("type") == "agent_message" for item in req["input"]): + import gecis_context + req["input"] = gecis_context.portable_reasoning(req)["input"] custom = set() tools = [] tool_names = tool_names if tool_names is not None else {} @@ -1031,13 +1035,13 @@ class H(http.server.BaseHTTPRequestHandler): if jev_denetim.muaf_mi(hedef): return self._stream(resp, resp.status, tag, rewriter, izle, basliksiz=True) if gecmedi: - # Son katman ne dondurduyse o gider, hicbir sey eklenmez; dondurmediyse hicbir sey gitmez. - if son_govde: - w(son_govde) - else: - event = {"type": "response.failed", "response": {"status": "failed", "error": { - "code": "jev_no_fallback", "message": "Jev denetiminden gecen cevap alinamadi; gecis kurallarini kontrol edin."}}} - w(b"event: response.failed\ndata: " + json.dumps(event).encode() + b"\n\n") + # Headers already declare SSE. A raw upstream JSON/HTML body + # here leaves Codex waiting for a terminal event and retrying. + event = {"type": "response.failed", "response": { + "id": "resp_astrabridge_%d" % time.time_ns(), + "object": "response", "status": "failed", "output": [], "error": { + "code": "jev_no_fallback", "message": "Jev denetiminden gecen cevap alinamadi; gecis kurallarini kontrol edin."}}} + w(b"event: response.failed\ndata: " + json.dumps(event).encode() + b"\n\n") else: for chunk in parcalar: if izle is not None: @@ -1747,7 +1751,7 @@ class H(http.server.BaseHTTPRequestHandler): yanit = urllib.request.urlopen(urllib.request.Request( jev_rota["url"], data=json.dumps(katman_req, ensure_ascii=False).encode("utf-8"), headers=jev_hdrs, method="POST"), timeout=UPSTREAM_IDLE_TIMEOUT) - return (yanit, SseRewriter(custom, hedef=ad, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren), + return (yanit, SseRewriter(custom, hedef=ad, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren, tool_names=tool_names), f"[{n} {slug}=>{ad}]") # A 9Router combo (Fusion-*, no provider prefix) runs a whole panel upstream. Once it may # have started (timeout, 504, cut stream) resending it runs and bills it twice (28 Sep 2026); diff --git a/apps/desktop/assets/codex-bridge/pencere.py b/apps/desktop/assets/codex-bridge/pencere.py index 511899c..7576694 100644 --- a/apps/desktop/assets/codex-bridge/pencere.py +++ b/apps/desktop/assets/codex-bridge/pencere.py @@ -221,7 +221,7 @@ def _yapraklar(dugum, derinlik=0): return cikti -def coz(kimlik, tur, yol, kayit=None, nano=None, ogr=None, aracsiz=()): +def coz(kimlik, tur, yol, kayit=None, nano=None, ogr=None, aracsiz=(), ilan=None): """Bir katalog girdisinin ilan edilecek penceresi ve yetenekleri. kimlik: gecit kimligi; tur: "combo" | "model"; yol: gecidin aciklamasindaki "saglayici/model" yolu; kayit: /router/catalog girdisi (combo topolojisi icin). Donus: {"pencere","gorsel","arac","kaynak"}.""" @@ -246,6 +246,13 @@ def coz(kimlik, tur, yol, kayit=None, nano=None, ogr=None, aracsiz=()): m = re.match(r"no-think-(?:9router-)?([a-z0-9]+)-", kimlik or "") yol = (m.group(1) + "/" + yol[len("no-think/"):]) if m else yol sonuc = yol_bilgisi(yol, nano) or dict(bilinmeyen) + # Exact /models metadata is stronger evidence than name-based local + # fallbacks. Do not infer a combo's capacity from its display name. + ilan = ilan if isinstance(ilan, dict) else {} + canli_sinirlar = [ilan[k] for k in ("context_length", "max_input_tokens") + if type(ilan.get(k)) is int and TABAN <= ilan[k] <= 10_000_000] + if canli_sinirlar: + sonuc = {**sonuc, "pencere": min(canli_sinirlar), "kaynak": "saglayici-katalogu"} if kimlik in aracsiz: sonuc = {**sonuc, "arac": False} sinir = (ogr or {}).get(kimlik) diff --git a/apps/desktop/assets/codex-bridge/test_gecis_context.py b/apps/desktop/assets/codex-bridge/test_gecis_context.py index 190cdc9..a4e5950 100644 --- a/apps/desktop/assets/codex-bridge/test_gecis_context.py +++ b/apps/desktop/assets/codex-bridge/test_gecis_context.py @@ -22,6 +22,26 @@ from test_model_switch import env class History(unittest.TestCase): + def test_agent_task_survives_external_projection_and_recent_history(self): + task = {"type": "agent_message", "id": "amsg_task", "author": "/root", + "recipient": "/root/child", "content": [ + {"type": "input_text", "text": "Message Type: NEW_TASK\nRun CHILD_TOOL_410962"}]} + req = {"input": [{"role": "user", "content": "Old setup"}, task]} + before = copy.deepcopy(req) + self.assertEqual(gc.normalize(req), before) + projected = gc.portable_reasoning(req) + message = projected["input"][-1] + self.assertEqual(message["role"], "user") + self.assertEqual(message["content"][1:], task["content"]) + self.assertIn('/root/child', message["content"][0]["text"]) + self.assertNotIn("id", message) + self.assertEqual(gc.split_history(projected["input"], keep_users=1)[2], [message]) + self.assertEqual(req, before) + self.assertEqual(gc.portable_reasoning(projected), projected) + direct = copy.deepcopy(req) + env["flatten_request"](direct) + self.assertEqual(direct["input"][-1], message) + def test_native_function_results_are_not_foreign_or_renamed(self): req = {"input": [{"type": "function_call_output", "id": "fco_native", "call_id": "call_native", "output": "kept"}]} diff --git a/apps/desktop/assets/codex-bridge/test_jev_denetim.py b/apps/desktop/assets/codex-bridge/test_jev_denetim.py index 659c592..7e320bc 100644 --- a/apps/desktop/assets/codex-bridge/test_jev_denetim.py +++ b/apps/desktop/assets/codex-bridge/test_jev_denetim.py @@ -335,7 +335,7 @@ class Akis(unittest.TestCase): self.assertNotIn(b"yapamam", self.h.wfile.getvalue()) self.assertIn(b'response.failed', self.sent()) - def test_the_last_layers_own_error_body_is_delivered_untouched(self): + def test_last_layer_http_error_terminates_sse_without_leaking_raw_body(self): class Hata(Exception): def read(self): return b'{"error": {"message": "katman 4 hatasi"}}' @@ -344,7 +344,14 @@ class Akis(unittest.TestCase): def ac(ad): raise Hata("HTTP 400") self.h._denetimli_akis(Yanit(sse("Bunu yapamam.")), "[1]", None, None, "yap", ["Katman-4"], ac) - self.assertEqual(self.sent(), b'{"error": {"message": "katman 4 hatasi"}}') + raw = self.sent() + self.assertTrue(raw.startswith(b"event: response.failed\ndata: ")) + event = json.loads(raw.split(b"data: ", 1)[1]) + self.assertEqual(event["response"]["status"], "failed") + self.assertEqual(event["response"]["error"]["code"], "jev_no_fallback") + self.assertEqual(event["response"]["output"], []) + self.assertNotIn(b"katman 4 hatasi", raw) + self.assertNotIn(b"Bunu yapamam", raw) def test_unreachable_jev_keeps_the_answer(self): self.puanlar = [None] diff --git a/apps/desktop/assets/codex-bridge/test_live_limits.py b/apps/desktop/assets/codex-bridge/test_live_limits.py new file mode 100644 index 0000000..2b9c3ce --- /dev/null +++ b/apps/desktop/assets/codex-bridge/test_live_limits.py @@ -0,0 +1,18 @@ +import pencere + + +def test_exact_combo_limits_override_unknown_and_local_defaults(): + row = {"context_length": 872000, "max_input_tokens": 872000, "max_output_tokens": 128000} + result = pencere.coz("GPT-6-Astra", "combo", None, ilan=row) + assert result["pencere"] == 872000 + assert result["kaynak"] == "saglayici-katalogu" + result = pencere.coz("codex-gpt-6-astra", "model", "codex/gpt-6-astra", ilan=row) + assert result["pencere"] == 872000 + + +def test_learned_actual_limit_still_wins_and_invalid_metadata_is_ignored(): + result = pencere.coz("combo", "combo", None, ilan={"context_length": 872000}, ogr={"combo": 500000}) + assert result["pencere"] == 500000 and result["kaynak"] == "ogrenilen" + for value in (True, -1, "872000", 999999999): + result = pencere.coz("combo", "combo", None, ilan={"context_length": value}) + assert result["pencere"] == pencere.VARSAYILAN diff --git a/apps/desktop/electron/codex-link-config.ts b/apps/desktop/electron/codex-link-config.ts index 56d843a..5a3e7af 100644 --- a/apps/desktop/electron/codex-link-config.ts +++ b/apps/desktop/electron/codex-link-config.ts @@ -27,7 +27,7 @@ export const CODEX_LINK_REFRESH_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama export const CODEX_LINK_MARKER = '# AITURK IDE Codex Baglama' // Must equal SURUM in assets/codex-bridge/bridge.py; a running bridge that reports // another version is restarted so an updated IDE never keeps an old bridge alive. -export const CODEX_BRIDGE_VERSION = '14' +export const CODEX_BRIDGE_VERSION = '15' export const CODEX_LINK_LINE = `openai_base_url = "${CODEX_LINK_ORIGIN}/backend-api/codex" ${CODEX_LINK_MARKER}` export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 230fcaa..fe9d92a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -2,7 +2,7 @@ "name": "aiturk-ide", "productName": "AITURK IDE", "private": true, - "version": "1.0.0-beta.33", + "version": "1.0.0-beta.34", "description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.", "author": "AITURK / TurkServis", "repository": { diff --git a/package-lock.json b/package-lock.json index 4b2dcc4..7c86abc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ }, "apps/desktop": { "name": "aiturk-ide", - "version": "1.0.0-beta.33", + "version": "1.0.0-beta.34", "license": "MIT", "dependencies": { "@assistant-ui/core": "0.2.23",