Compare commits

...
Author SHA1 Message Date
yilsem db5e876f40 fix(bridge): preserve plaintext peer task payloads for external models
Server CI and Windows package / verify (push) Successful in 3m16s
Server CI and Windows package / windows-package (push) Successful in 8m18s
2026-10-13 08:05:00 +03:00
yilsem ab7a721fbc Ship beta34 with external agent tasks, terminal SSE and live token limits
Server CI and Windows package / verify (push) Successful in 3m13s
Server CI and Windows package / windows-package (push) Successful in 7m56s
2026-10-13 07:32:57 +03:00
yilsem 2cca6a66a6 Preserve namespaced Codex tools across external model calls
Server CI and Windows package / verify (push) Successful in 2m11s
Server CI and Windows package / windows-package (push) Successful in 10m44s
2026-10-13 04:54:52 +03:00
16 changed files with 290 additions and 28 deletions

No files matched your search

+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Codex bridge regression tests - name: Codex bridge regression tests
run: '"$RUNNER_TEMP/ci-venv/bin/python" -m unittest discover -s apps/desktop/assets/codex-bridge' run: '"$RUNNER_TEMP/ci-venv/bin/python" -m unittest discover -s apps/desktop/assets/codex-bridge'
- name: Native watch and speech mux pytest regressions - 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 - name: Mobile and desktop startup regression tests
run: | 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 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
+5 -2
View File
@@ -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, 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. 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. 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. 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 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ış Dağıtılacak derleme temiz, yayımlanmış Git commit'inden üretilmelidir. İlk açılış
ajan kurulumunu paketteki `install-stamp.json` commit'ine sabitler. Windows 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 paketleri imzasız EXE/ZIP olarak yayımlanır; SHA-256 ve gerçek Windows çalıştırma
sunulmamalıdır. macOS ve Linux paketleri bu beta kapsamında doğrulanmamıştır. 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 ## Lisans ve kaynak
+1 -1
View File
@@ -34,7 +34,7 @@ import urllib.request
import gecis_context import gecis_context
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
SURUM = "13" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION) SURUM = "16" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
try: try:
with open(os.path.abspath(__file__), "rb") as _f: with open(os.path.abspath(__file__), "rb") as _f:
KOD = hashlib.sha256(_f.read()).hexdigest()[:8] KOD = hashlib.sha256(_f.read()).hexdigest()[:8]
@@ -7,6 +7,7 @@ must not enter the budget preparation path.
import copy import copy
import hashlib import hashlib
import json import json
import re
import threading import threading
from collections import OrderedDict from collections import OrderedDict
@@ -104,6 +105,31 @@ def portable_reasoning(req):
return out return out
result = [] result = []
for item in items: 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}]
portable = []
for part in content:
if isinstance(part, dict) and part.get("type") == "encrypted_content":
# Codex peer delivery can put the literal task/result in
# this wrapper. It is distinct from encrypted reasoning.
# External translators otherwise silently drop the task.
text = part.get("encrypted_content")
if (not isinstance(text, str) or text.startswith("gAAAA")
or re.fullmatch(r"[A-Za-z0-9_+/=-]{128,}", text)):
raise TransferError("Alt ajan iletisi şifreli veya okunamıyor; görev eksik gönderilmedi.")
portable.append({"type": "input_text", "text": text})
else:
portable.append(part)
result.append({"type": "message", "role": "user", "content": [
{"type": "input_text", "text": "Agent message: " + encoded({
"author": item.get("author"), "recipient": item.get("recipient")})}
] + portable})
continue
if not isinstance(item, dict) or item.get("type") != "reasoning": if not isinstance(item, dict) or item.get("type") != "reasoning":
result.append(item) result.append(item)
continue continue
@@ -348,11 +348,13 @@ def yukle(ua="AstraBridge/1.0 (codex)", timeout=30, home=None, ag=True):
if not girdiler: if not girdiler:
raise ValueError("Gecitte kullanilabilir OmniRoute sohbet modeli yok") raise ValueError("Gecitte kullanilabilir OmniRoute sohbet modeli yok")
kayitlar = {m["id"]: m for m in metadata if m.get("id")} 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 nano = pencere.nano_yukle(home, ua, ag=ag) # ag=False: acilista yalniz onbellek
ogr = pencere.ogrenilen(home) ogr = pencere.ogrenilen(home)
aracsiz = pencere.aracsizlar(home) aracsiz = pencere.aracsizlar(home)
for g in girdiler: 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"], g["pencere"], g["gorsel"], g["arac"], g["pencere_kaynak"] = (bilgi["pencere"], bilgi["gorsel"],
bilgi["arac"], bilgi["kaynak"]) bilgi["arac"], bilgi["kaynak"])
g["claude"] = pencere.claude_var(g["kind"], g.get("yol"), kayitlar.get(g["id"])) g["claude"] = pencere.claude_var(g["kind"], g.get("yol"), kayitlar.get(g["id"]))
+72 -16
View File
@@ -40,7 +40,7 @@ from bekci_watch import MetadataFilter
# Installed only by owned native launcher, never an HTTP control endpoint. # Installed only by owned native launcher, never an HTTP control endpoint.
WATCH_OWNER = None WATCH_OWNER = None
SURUM = "13" SURUM = "16"
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
try: try:
with open(os.path.abspath(__file__), "rb") as _f: with open(os.path.abspath(__file__), "rb") as _f:
@@ -363,15 +363,40 @@ def arac_semalarini_duzelt(req):
n += bool(degisti) n += bool(degisti)
return n return n
def flatten_request(req): def flatten_request(req, tool_names=None):
"""additional_tools/namespace/custom -> duz function tools. Donus: custom arac adlari kumesi.""" """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() custom = set()
tools = [] tools = []
def add_tool(t): tool_names = tool_names if tool_names is not None else {}
identities = {}
wire_names = {}
def add_tool(t, namespace=None):
ty = t.get("type") ty = t.get("type")
if ty in ("function", "custom"):
namespace = t.get("namespace") or namespace
name = t["name"]
if namespace:
import hashlib
identity = (namespace, name)
# A hash avoids collisions between underscore-containing namespace/name pairs.
alias = "ns_" + hashlib.sha256(json.dumps(identity).encode()).hexdigest()[:24] + "_" + re.sub(r"[^a-zA-Z0-9_-]", "_", name)[:32]
if alias in tool_names and tool_names[alias] != identity:
raise ValueError("Tool namespace alias collision")
tool_names[alias] = identity
identities[identity] = alias
t = dict(t, name=alias, description=f"Tool {namespace}.{name}. " + t.get("description", ""))
wire = t["name"]
identity = (namespace, name)
if wire in wire_names and wire_names[wire] != identity:
raise ValueError("Tool namespace alias collision")
wire_names[wire] = identity
if ty == "namespace": if ty == "namespace":
for s in t.get("tools", []): for s in t.get("tools", []):
add_tool(s) add_tool(s, ".".join(filter(None, (namespace, t.get("name")))))
elif ty == "function": elif ty == "function":
tools.append({"type": "function", "name": t["name"], "description": t.get("description", ""), tools.append({"type": "function", "name": t["name"], "description": t.get("description", ""),
"parameters": t.get("parameters") or {"type": "object", "properties": {}}, "strict": False}) "parameters": t.get("parameters") or {"type": "object", "properties": {}}, "strict": False})
@@ -387,13 +412,34 @@ def flatten_request(req):
tools.append(t) tools.append(t)
for t in req.get("tools", []) or []: for t in req.get("tools", []) or []:
add_tool(t) add_tool(t)
for it in req.get("input", []) or []:
if it.get("type") == "additional_tools":
for t in it.get("tools", []):
add_tool(t)
plain_names = {t.get("name") for t in tools if t.get("name") not in tool_names}
def flatten_name(item):
namespace, name = item.get("namespace"), item.get("name")
alias = identities.get((namespace, name))
if not namespace and name not in plain_names:
matches = [a for a, identity in tool_names.items() if identity[1] == name]
if len(matches) == 1:
alias = matches[0] # Legacy history recorded before namespaces were preserved.
if alias:
item = dict(item, name=alias)
item.pop("namespace", None)
return item
choice = req.get("tool_choice")
if isinstance(choice, dict):
req["tool_choice"] = flatten_name(choice)
if isinstance(choice.get("tools"), list):
req["tool_choice"] = dict(req["tool_choice"], tools=[flatten_name(t) for t in choice["tools"]])
new_input = [] new_input = []
for it in req.get("input", []) or []: for it in req.get("input", []) or []:
ty = it.get("type") ty = it.get("type")
if ty == "additional_tools": if ty == "additional_tools":
for t in it.get("tools", []):
add_tool(t)
continue continue
if ty in ("function_call", "custom_tool_call"):
it = flatten_name(it)
if ty in ("function_call_output", "custom_tool_call_output") and not it.get("call_id"): if ty in ("function_call_output", "custom_tool_call_output") and not it.get("call_id"):
# Desktop thread handoffs have no matching tool call. Preserve their # Desktop thread handoffs have no matching tool call. Preserve their
# content as imported context, not an invalid tool-role message. # content as imported context, not an invalid tool-role message.
@@ -527,9 +573,10 @@ class SseRewriter:
Ayrica dis modelin asistan mesajlarina phase damgasi koyar: mesajin "bitti" olayi, arkasindan ne Ayrica dis modelin asistan mesajlarina phase damgasi koyar: mesajin "bitti" olayi, arkasindan ne
geldigi belli olana kadar bekletilir (arac cagrisi ya da baska oge -> commentary; yanit bitti -> geldigi belli olana kadar bekletilir (arac cagrisi ya da baska oge -> commentary; yanit bitti ->
final_answer). Metin parcalari (delta) bekletilmez, canli akar.""" final_answer). Metin parcalari (delta) bekletilmez, canli akar."""
def __init__(self, custom, hedef=None, tasma=None, aracsiz=None, dolu=False): def __init__(self, custom, hedef=None, tasma=None, aracsiz=None, dolu=False, tool_names=None):
self.dolu = dolu # istek hedef pencerenin buyuk kismini dolduruyor self.dolu = dolu # istek hedef pencerenin buyuk kismini dolduruyor
self.custom = custom self.custom = custom
self.tool_names = tool_names or {}
self.buf = b"" self.buf = b""
self.custom_ids = set() self.custom_ids = set()
self.bekleyen = None # phase karari bekleyen "output_item.done" olayi self.bekleyen = None # phase karari bekleyen "output_item.done" olayi
@@ -634,6 +681,12 @@ class SseRewriter:
self.bekleyen = d self.bekleyen = d
return once return once
return once + [d] return once + [d]
def _restore_tool_name(self, item):
if isinstance(item, dict) and item.get("type") in ("function_call", "custom_tool_call"):
identity = self.tool_names.get(item.get("name"))
if identity:
item["namespace"], item["name"] = identity
return item
def _fix(self, d): def _fix(self, d):
t = d.get("type", "") t = d.get("type", "")
item = d.get("item") item = d.get("item")
@@ -655,6 +708,7 @@ class SseRewriter:
# Normalize before Codex persists the response. Request-only repair # Normalize before Codex persists the response. Request-only repair
# cannot help when the user later disables the bridge. # cannot help when the user later disables the bridge.
fix_tool_ids({"input": [d["item"]]}) fix_tool_ids({"input": [d["item"]]})
self._restore_tool_name(d["item"])
return d return d
if t.startswith("response.function_call_arguments.") and d.get("item_id") in self.custom_ids: if t.startswith("response.function_call_arguments.") and d.get("item_id") in self.custom_ids:
return None # JSON parcalari custom input'a cevrilemez; done olayinda tam metin gidiyor return None # JSON parcalari custom input'a cevrilemez; done olayinda tam metin gidiyor
@@ -667,9 +721,10 @@ class SseRewriter:
item = {"type": "custom_tool_call", "id": item.get("id"), "call_id": item.get("call_id"), item = {"type": "custom_tool_call", "id": item.get("id"), "call_id": item.get("call_id"),
"name": item.get("name"), "input": inp, "status": "completed"} "name": item.get("name"), "input": inp, "status": "completed"}
fix_tool_ids({"input": [item]}) fix_tool_ids({"input": [item]})
out.append(item) out.append(self._restore_tool_name(item))
d["response"]["output"] = out d["response"]["output"] = out
fix_tool_ids({"input": out}) fix_tool_ids({"input": out})
self._restore_tool_name(d.get("item"))
return d return d
def feed(self, chunk): def feed(self, chunk):
self.buf += chunk self.buf += chunk
@@ -980,11 +1035,11 @@ class H(http.server.BaseHTTPRequestHandler):
if jev_denetim.muaf_mi(hedef): if jev_denetim.muaf_mi(hedef):
return self._stream(resp, resp.status, tag, rewriter, izle, basliksiz=True) return self._stream(resp, resp.status, tag, rewriter, izle, basliksiz=True)
if gecmedi: if gecmedi:
# Son katman ne dondurduyse o gider, hicbir sey eklenmez; dondurmediyse hicbir sey gitmez. # Headers already declare SSE. A raw upstream JSON/HTML body
if son_govde: # here leaves Codex waiting for a terminal event and retrying.
w(son_govde) event = {"type": "response.failed", "response": {
else: "id": "resp_astrabridge_%d" % time.time_ns(),
event = {"type": "response.failed", "response": {"status": "failed", "error": { "object": "response", "status": "failed", "output": [], "error": {
"code": "jev_no_fallback", "message": "Jev denetiminden gecen cevap alinamadi; gecis kurallarini kontrol edin."}}} "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") w(b"event: response.failed\ndata: " + json.dumps(event).encode() + b"\n\n")
else: else:
@@ -1623,11 +1678,12 @@ class H(http.server.BaseHTTPRequestHandler):
log(f"{tag} {cikan} resim not ile degistirildi ({kazanc // 1024} KB)") log(f"{tag} {cikan} resim not ile degistirildi ({kazanc // 1024} KB)")
rewriter = None rewriter = None
if flat if flat is not None else route.get("flatten_tools"): if flat if flat is not None else route.get("flatten_tools"):
custom = flatten_request(req) tool_names = {}
custom = flatten_request(req, tool_names)
duzelen = arac_semalarini_duzelt(req) duzelen = arac_semalarini_duzelt(req)
if duzelen: if duzelen:
log(f"{tag} {duzelen} aracin semasi duzeltildi (ust duzey oneOf/anyOf/allOf)") log(f"{tag} {duzelen} aracin semasi duzeltildi (ust duzey oneOf/anyOf/allOf)")
rewriter = SseRewriter(custom, hedef=target, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren) rewriter = SseRewriter(custom, hedef=target, tasma=self._tasma_ogren, aracsiz=self._aracsiz_ogren, tool_names=tool_names)
fix_tool_ids(req) fix_tool_ids(req)
if route_name == catalog.get('route', '9router') and target.startswith('ag/gemini-'): if route_name == catalog.get('route', '9router') and target.startswith('ag/gemini-'):
merged = coalesce_context(req) merged = coalesce_context(req)
@@ -1695,7 +1751,7 @@ class H(http.server.BaseHTTPRequestHandler):
yanit = urllib.request.urlopen(urllib.request.Request( 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"), jev_rota["url"], data=json.dumps(katman_req, ensure_ascii=False).encode("utf-8"), headers=jev_hdrs, method="POST"),
timeout=UPSTREAM_IDLE_TIMEOUT) 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}]") f"[{n} {slug}=>{ad}]")
# A 9Router combo (Fusion-*, no provider prefix) runs a whole panel upstream. Once it may # 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); # have started (timeout, 504, cut stream) resending it runs and bills it twice (28 Sep 2026);
+8 -1
View File
@@ -221,7 +221,7 @@ def _yapraklar(dugum, derinlik=0):
return cikti 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. """Bir katalog girdisinin ilan edilecek penceresi ve yetenekleri.
kimlik: gecit kimligi; tur: "combo" | "model"; yol: gecidin aciklamasindaki "saglayici/model" yolu; kimlik: gecit kimligi; tur: "combo" | "model"; yol: gecidin aciklamasindaki "saglayici/model" yolu;
kayit: /router/catalog girdisi (combo topolojisi icin). Donus: {"pencere","gorsel","arac","kaynak"}.""" 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 "") m = re.match(r"no-think-(?:9router-)?([a-z0-9]+)-", kimlik or "")
yol = (m.group(1) + "/" + yol[len("no-think/"):]) if m else yol yol = (m.group(1) + "/" + yol[len("no-think/"):]) if m else yol
sonuc = yol_bilgisi(yol, nano) or dict(bilinmeyen) 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: if kimlik in aracsiz:
sonuc = {**sonuc, "arac": False} sonuc = {**sonuc, "arac": False}
sinir = (ogr or {}).get(kimlik) sinir = (ogr or {}).get(kimlik)
@@ -0,0 +1,52 @@
"""Regression fixture matching actual Codex child delivery, with no user data."""
import copy
import unittest
import gecis_context
import kopru
class AgentPayloadTests(unittest.TestCase):
def request(self, text):
return {"model": "ide/test", "input": [{
"type": "agent_message", "author": "/root", "recipient": "/root/test",
"content": [
{"type": "input_text", "text": "Message Type: NEW_TASK\nPayload:\n"},
{"type": "encrypted_content", "encrypted_content": text},
],
}]}
def test_actual_peer_wrapper_survives_external_text_extraction(self):
task = "Run exec_command: Write-Output (157 * 29); report the exit code."
request = self.request(task)
original = copy.deepcopy(request)
kopru.flatten_request(request)
# An OpenAI-compatible translator consumes input_text, not Codex's
# encrypted_content part. Checking the JSON string alone misses loss.
text = "\n".join(part["text"] for item in request["input"]
for part in item["content"] if part["type"] == "input_text")
self.assertIn(task, text)
self.assertIn('/root/test', text)
self.assertEqual(request["input"][0]["role"], "user")
self.assertEqual(original["input"][0]["content"][1]["type"], "encrypted_content")
def test_peer_result_and_plain_parts_keep_order_without_source_mutation(self):
request = self.request("Result: 4553; exit code: 0")
original = copy.deepcopy(request)
result = gecis_context.portable_reasoning(request)
self.assertEqual(result["input"][0]["content"][-1]["text"], "Result: 4553; exit code: 0")
self.assertEqual(request, original)
def test_opaque_peer_payload_fails_instead_of_sending_header_only(self):
for opaque in ("gAAAA_native_ciphertext", "A" * 200, None):
with self.subTest(opaque=opaque):
with self.assertRaises(gecis_context.TransferError):
gecis_context.portable_reasoning(self.request(opaque))
def test_reasoning_ciphertext_is_still_not_interpreted_as_a_peer_task(self):
request = {"input": [{"type": "reasoning", "encrypted_content": "Run a command now"}]}
self.assertEqual(gecis_context.portable_reasoning(request)["input"], [])
if __name__ == "__main__":
unittest.main()
@@ -22,6 +22,26 @@ from test_model_switch import env
class History(unittest.TestCase): 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): def test_native_function_results_are_not_foreign_or_renamed(self):
req = {"input": [{"type": "function_call_output", "id": "fco_native", req = {"input": [{"type": "function_call_output", "id": "fco_native",
"call_id": "call_native", "output": "kept"}]} "call_id": "call_native", "output": "kept"}]}
@@ -335,7 +335,7 @@ class Akis(unittest.TestCase):
self.assertNotIn(b"yapamam", self.h.wfile.getvalue()) self.assertNotIn(b"yapamam", self.h.wfile.getvalue())
self.assertIn(b'response.failed', self.sent()) 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): class Hata(Exception):
def read(self): def read(self):
return b'{"error": {"message": "katman 4 hatasi"}}' return b'{"error": {"message": "katman 4 hatasi"}}'
@@ -344,7 +344,14 @@ class Akis(unittest.TestCase):
def ac(ad): def ac(ad):
raise Hata("HTTP 400") raise Hata("HTTP 400")
self.h._denetimli_akis(Yanit(sse("Bunu yapamam.")), "[1]", None, None, "yap", ["Katman-4"], ac) 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): def test_unreachable_jev_keeps_the_answer(self):
self.puanlar = [None] self.puanlar = [None]
@@ -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
@@ -43,7 +43,12 @@ class ModelSwitch(unittest.TestCase):
{"type": "custom_tool_call_output", "id": "ctco_4827", "call_id": "call_4827", "output": "result-4827"}, {"type": "custom_tool_call_output", "id": "ctco_4827", "call_id": "call_4827", "output": "result-4827"},
], "tools": [{"type": "namespace", "name": "functions", "tools": [{"type": "custom", "name": "probe"}]}]} ], "tools": [{"type": "namespace", "name": "functions", "tools": [{"type": "custom", "name": "probe"}]}]}
req = copy.deepcopy(original) req = copy.deepcopy(original)
self.assertEqual(env["flatten_request"](req), {"probe"}) names = {}
custom = env["flatten_request"](req, names)
alias = req["tools"][0]["name"]
self.assertEqual(custom, {alias})
self.assertEqual(names[alias], ("functions", "probe"))
self.assertEqual(req["input"][0]["name"], alias)
env["fix_tool_ids"](req) env["fix_tool_ids"](req)
self.assertEqual(json.loads(req["input"][0]["arguments"]), {"input": "remember-4827"}) self.assertEqual(json.loads(req["input"][0]["arguments"]), {"input": "remember-4827"})
self.assertEqual(req["input"][1]["output"], "result-4827") self.assertEqual(req["input"][1]["output"], "result-4827")
@@ -0,0 +1,66 @@
"""Namespace routing regressions: leaf-only calls cannot dispatch collaboration tools."""
import copy
import json
import unittest
from test_model_switch import env
class ToolNamespaces(unittest.TestCase):
def request(self):
return {"tools": [
{"type": "namespace", "name": "collaboration", "tools": [{"type": "function", "name": "spawn_agent", "parameters": {"type": "object", "properties": {}}}]},
{"type": "namespace", "name": "other", "tools": [{"type": "function", "name": "spawn_agent"}]},
{"type": "namespace", "name": "functions", "tools": [{"type": "custom", "name": "apply_patch"}]},
{"type": "function", "name": "plain"}],
"input": [{"type": "function_call", "namespace": "collaboration", "name": "spawn_agent", "id": "fc_a", "call_id": "call_a", "arguments": "{}"},
{"type": "function_call_output", "call_id": "call_a", "output": "child_123"}]}
def test_namespace_calls_round_trip_at_all_stream_stages(self):
req = self.request(); original = copy.deepcopy(req); names = {}
custom = env["flatten_request"](req, names)
alias = req["tools"][0]["name"]
self.assertNotEqual(alias, req["tools"][1]["name"])
self.assertLessEqual(len(alias), 64)
self.assertEqual(req["input"][0]["name"], alias)
self.assertNotIn("namespace", req["input"][0])
self.assertEqual(req["input"][1], original["input"][1])
adapter = env["SseRewriter"](custom, tool_names=names)
item = {"type": "function_call", "name": alias, "id": "fc_test", "call_id": "call_test", "arguments": "{}"}
for stage in ["response.output_item.added", "response.output_item.done"]:
restored = adapter._fix({"type": stage, "item": dict(item)})["item"]
self.assertEqual((restored["namespace"], restored["name"]), ("collaboration", "spawn_agent"))
self.assertEqual(restored["call_id"], "call_test")
restored = adapter._fix({"type": "response.completed", "response": {"output": [dict(item)]}})["response"]["output"][0]
self.assertEqual((restored["namespace"], restored["name"]), ("collaboration", "spawn_agent"))
def test_custom_tool_namespace_and_ids_survive_chunked_sse(self):
req = self.request(); names = {}; custom = env["flatten_request"](req, names)
alias = req["tools"][2]["name"]
item = {"type": "function_call", "name": alias, "id": "fc_patch", "call_id": "call_patch", "arguments": json.dumps({"input": "patch text"})}
raw = ("data: " + json.dumps({"type": "response.output_item.done", "item": item}) + "\n\n").encode()
a = env["SseRewriter"](custom, tool_names=names)
output = a.feed(raw[:31]) + a.feed(raw[31:]) + a.flush()
restored = json.loads(next(l[6:] for l in output.decode().splitlines() if l.startswith("data: ")))["item"]
self.assertEqual((restored["namespace"], restored["name"], restored["type"]), ("functions", "apply_patch", "custom_tool_call"))
self.assertEqual(restored["id"], "ctc_patch")
self.assertEqual(restored["input"], "patch text")
def test_plain_tools_stay_plain_and_history_not_mutated(self):
req = self.request(); original = copy.deepcopy(req); old_item = req["input"][0]
env["flatten_request"](req, {})
self.assertEqual(req["tools"][-1]["name"], "plain")
self.assertEqual(old_item, original["input"][0])
plain = {"type": "function_call", "name": "plain", "arguments": "{}"}
self.assertEqual(env["SseRewriter"](set())._fix({"item": plain})["item"], plain)
def test_additional_tools_long_names_and_tool_choice(self):
namespace = "provider_" * 20
req = {"tools": [], "input": [{"type": "additional_tools", "tools": [{"type": "namespace", "name": namespace, "tools": [{"type": "function", "name": "read"}]}]}], "tool_choice": {"type": "function", "namespace": namespace, "name": "read"}}
names = {}; env["flatten_request"](req, names)
alias = req["tools"][0]["name"]
self.assertLessEqual(len(alias), 64)
self.assertEqual(req["tool_choice"], {"type": "function", "name": alias})
self.assertEqual(req["input"], [])
self.assertEqual(names[alias], (namespace, "read"))
if __name__ == "__main__": unittest.main()
+1 -1
View File
@@ -27,7 +27,7 @@ export const CODEX_LINK_REFRESH_URL = `${CODEX_LINK_ORIGIN}/aiturk/codex-baglama
export const CODEX_LINK_MARKER = '# AITURK IDE Codex Baglama' 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 = '13' export const CODEX_BRIDGE_VERSION = '16'
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_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' export const CODEX_LINK_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
+1 -1
View File
@@ -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.32", "version": "1.0.0-beta.35",
"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": {
+1 -1
View File
@@ -65,7 +65,7 @@
}, },
"apps/desktop": { "apps/desktop": {
"name": "aiturk-ide", "name": "aiturk-ide",
"version": "1.0.0-beta.32", "version": "1.0.0-beta.35",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@assistant-ui/core": "0.2.23", "@assistant-ui/core": "0.2.23",