fix(codex-link): omit opaque execution state on provider switches
Server CI and Windows package / verify (push) Successful in 6m55s
Server CI and Windows package / windows-package (push) Canceled after 15m40s

Preserve visible history with omission notes and original encrypted state on native return. Ship IDE beta.45.
This commit is contained in:
yilsem committed 2026-10-20 20:24:47 +03:00
1 parent 21a8f3b8e9
commit db46a470ea
8 files changed
+88 -62

No files matched your search

@@ -27,6 +27,24 @@ Run: `python bridge.py --home <data dir> --hermes-home <agent home>`. The IDE wr
Tests: `python -m unittest discover` in this directory.
## Version 23: portable model switches (IDE beta.45)
Cross-provider requests replace encrypted reasoning and every typed encrypted
peer-message part with a short visible omission note. Ciphertext is never
interpreted, guessed from its alphabet, or forwarded to another provider. Visible
messages, peer text, tool calls/results and their order remain intact. Projection
is idempotent and changes only the outgoing copy. Returning to a subscription
model retains its original encrypted execution state, including large histories.
Actual conversation compaction is still rendered through its issuing endpoint;
unreadable conversation summaries retain the existing explicit error.
Other external-provider switches reuse the same transcript, with destination
context preparation only when necessary. This follows the Hermes runtime model
switch contract: `agent/agent_runtime_helpers.py::switch_model` updates the client,
provider, cache policy and compressor without resetting conversation history;
`website/docs/user-guide/configuring-models.md` documents in-session `/model`.
Native voice routing is unchanged.
## Version 19: decision stages (14 October 2026)
The decision model (Jev or another System One compatible service such as Celeris) can now be asked at
+3 -11
View File
@@ -35,7 +35,7 @@ import gecis_context
import terminal_stream
HERE = os.path.dirname(os.path.abspath(__file__))
SURUM = "22" # AITURK IDE'nin bekledigi kopru surumu (codex-link-config.ts CODEX_BRIDGE_VERSION)
SURUM = "23" # 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]
@@ -521,16 +521,6 @@ class H(http.server.BaseHTTPRequestHandler):
except Exception:
req = {}
if external:
# Opaque peer tasks cannot be sent to another provider. Detect
# this permanent transfer error before committing SSE headers:
# stock Codex retries unknown response.failed stream errors.
# Projection validates a copy; the saved/request history stays
# intact and no task is dropped or sent to the worker.
try:
gecis_context.portable_reasoning(req)
except gecis_context.TransferError as e:
return self._json(400, {"error": {"type": "invalid_request_error",
"code": "aiturk_transfer_failed", "message": str(e), "retryable": False}})
self._failure_key = terminal_stream.request_key(req or {"raw": govde.hex()}, self.headers)
previous_failure = self.server.failure_replays.take(self._failure_key)
if previous_failure:
@@ -547,6 +537,8 @@ class H(http.server.BaseHTTPRequestHandler):
if external or mixed or gecis_context.foreign(req):
try:
normalized = gecis_context.normalize(req)
if external:
normalized = gecis_context.portable_reasoning(normalized)
except gecis_context.TransferError as e:
self._transfer_begin()
return self._transfer_fail(str(e))
@@ -7,7 +7,6 @@ must not enter the budget preparation path.
import copy
import hashlib
import json
import re
import threading
from collections import OrderedDict
@@ -17,6 +16,8 @@ import compression_settings
PREFIXES = {"custom_tool_call": "ctc", "custom_tool_call_output": "ctco",
"function_call": "fc", "function_call_output": "fco"}
RATIO = 0.70
PEER_OMITTED = "[Aktarım notu: Burada okunamayan şifreli bir alt ajan iletisi vardı; aktarılmadı.]"
REASONING_OMITTED = "[Aktarım notu: Şifreli model düşünme kaydı aktarılmadı.]"
class TransferError(ValueError):
@@ -56,11 +57,16 @@ def foreign(req):
def native_opaque(req):
"""Native encrypted state belongs to the subscription, even in mixed history."""
return any(isinstance(item, dict) and item.get("type") == "compaction" and item.get("encrypted_content")
and not str(item["encrypted_content"]).startswith(ozet_kopru.ISARET)
return any(isinstance(item, dict) and (
(item.get("type") in ("compaction", "reasoning") and item.get("encrypted_content")
and not str(item["encrypted_content"]).startswith(ozet_kopru.ISARET))
or (item.get("type") == "agent_message" and any(
isinstance(part, dict) and part.get("type") == "encrypted_content"
for part in item.get("content") or [])))
for item in req.get("input") or [])
def foreign_history(req):
"""Semantic migration evidence, excluding tool ID format mismatches."""
return any(isinstance(item, dict) and (
@@ -135,14 +141,10 @@ def portable_reasoning(req):
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})
# Treat the typed wrapper as opaque regardless of whether
# its bytes happen to look like plaintext. Never decrypt,
# guess, or forward it to a different provider.
portable.append({"type": "input_text", "text": PEER_OMITTED})
else:
portable.append(part)
result.append({"type": "message", "role": "user", "content": [
@@ -158,6 +160,8 @@ def portable_reasoning(req):
for part in item.get(field) or []:
if isinstance(part, dict) and isinstance(part.get("text"), str) and part["text"]:
visible.append(part["text"])
if item.get("encrypted_content"):
visible.append(REASONING_OMITTED)
if visible:
# Keep this on the assistant side: a synthetic user message would
# change split_history's count of protected user turns.
@@ -17,7 +17,7 @@ class AgentPayloadTests(unittest.TestCase):
],
}]}
def test_actual_peer_wrapper_survives_external_text_extraction(self):
def test_typed_encrypted_wrapper_is_omitted_even_if_bytes_look_like_text(self):
task = "Run exec_command: Write-Output (157 * 29); report the exit code."
request = self.request(task)
original = copy.deepcopy(request)
@@ -26,7 +26,8 @@ class AgentPayloadTests(unittest.TestCase):
# 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.assertNotIn(task, text)
self.assertIn(gecis_context.PEER_OMITTED, text)
self.assertIn('/root/test', text)
self.assertEqual(request["input"][0]["role"], "user")
self.assertEqual(original["input"][0]["content"][1]["type"], "encrypted_content")
@@ -35,18 +36,22 @@ class AgentPayloadTests(unittest.TestCase):
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(result["input"][0]["content"][-1]["text"], gecis_context.PEER_OMITTED)
self.assertEqual(result["input"][0]["content"][1], original["input"][0]["content"][0])
self.assertEqual(request, original)
def test_opaque_peer_payload_fails_instead_of_sending_header_only(self):
def test_opaque_peer_payload_is_replaced_by_a_visible_note(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))
result = gecis_context.portable_reasoning(self.request(opaque))
self.assertEqual(result["input"][0]["content"][-1]["text"], gecis_context.PEER_OMITTED)
self.assertNotIn("encrypted_content", json.dumps(result))
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"], [])
result = gecis_context.portable_reasoning(request)
self.assertNotIn("Run a command now", json.dumps(result))
self.assertEqual(result["input"][0]["content"][0]["text"], gecis_context.REASONING_OMITTED)
class PlaintextAgentWireTests(unittest.TestCase):
@@ -68,7 +68,7 @@ class History(unittest.TestCase):
self.assertNotIn("OPAQUE_", json.dumps(result))
self.assertEqual(req, before)
self.assertLessEqual(gc.tokens(result), gc.input_budget(req, 32768))
self.assertEqual(result["input"][-1], {"role": "user", "content": "Latest"})
self.assertIn({"role": "user", "content": "Latest"}, result["input"])
def test_legacy_summary_and_tool_ids_repaired_without_mutating_source(self):
req = {"input": [ozet_kopru.ozet_ogesi("KARAR-4827 korunacak"),
@@ -259,19 +259,18 @@ class PreparationService(unittest.TestCase):
self.assertNotIn("OPAQUE_REASONING", json.dumps(result))
self.assertEqual(req, before)
def test_large_native_return_summarizes_visible_history_without_ciphertext(self):
def test_large_native_return_keeps_original_encrypted_execution_state(self):
for opaque in ({"type": "reasoning", "encrypted_content": "OPAQUE_NATIVE"},
{"type": "agent_message", "content": [
{"type": "encrypted_content", "encrypted_content": "OPAQUE_PEER"}]}):
req = {"model": "gpt-6-astra", "input": [
{"role": "user", "content": "KARAR-4827 " + "x" * 190000},
{"type": "reasoning", "encrypted_content": "OPAQUE_NATIVE"},
{"role": "user", "content": "Latest"}]}
opaque, {"role": "user", "content": "Latest"}]}
before = copy.deepcopy(req)
result = self.service._prepare_transfer({"request": req, "source_model": "ide/test"})["request"]
self.assertEqual(result["model"], "gpt-6-astra")
self.assertIn("KARAR-4827", json.dumps(result))
self.assertLessEqual(gc.tokens(result), gc.input_budget(req, 128000))
self.assertEqual(result, before)
self.assertEqual(req, before)
self.assertTrue(self.server.gelen)
self.assertTrue(all(b"OPAQUE_NATIVE" not in call["govde"] for call in self.server.gelen))
self.assertEqual(self.server.gelen, [])
def test_external_compaction_uses_external_summary_not_subscription(self):
req = {"model": "ide/test", "input": [{"role": "assistant", "content": "KARAR-4827"}, {"role": "user", "content": "KARAR-4827"}, {"type": "compaction_trigger"}]}
@@ -281,30 +280,38 @@ class PreparationService(unittest.TestCase):
class SocketMigration(fixtures.Ayirici):
def test_opaque_peer_transfer_fails_as_http_400_before_sse_or_upstream(self):
def test_opaque_peer_transfer_succeeds_without_ciphertext_or_transfer_error(self):
req = {"model": "ide/agy-gemini-3.8-flash-high", "prompt_cache_key": "opaque-peer",
"input": [{"type": "agent_message", "author": "/root", "recipient": "/root/peer",
"content": [{"type": "input_text", "text": "Message Type: NEW_TASK\nPayload:\n"},
{"type": "encrypted_content", "encrypted_content": "gAAAA_synthetic_opaque"}]}]}
"input": [{"role": "user", "content": "USER_DECISION"},
{"type": "agent_message", "author": "/root", "recipient": "/root/peer",
"content": [{"type": "input_text", "text": "VISIBLE_PEER_HEADER"},
{"type": "encrypted_content", "encrypted_content": "gAAAA_synthetic_opaque"}]},
{"role": "assistant", "content": "VISIBLE_ANSWER"},
{"type": "function_call", "id": "fc_1", "call_id": "c1", "name": "read", "arguments": "{}"},
{"type": "function_call_output", "id": "fco_1", "call_id": "c1", "output": "VISIBLE_RESULT"}]}
original = copy.deepcopy(req)
raw = json.dumps(req).encode()
# Identical retries must receive the same permanent error without a
# preparation request, a stream heartbeat, or provider inference.
for _ in range(2):
status, body, headers = self.iste("POST", "/backend-api/codex/responses", raw)
self.assertEqual(status, 400)
self.assertEqual(headers["Content-Type"].split(";")[0], "application/json")
self.assertNotIn("Transfer-Encoding", headers)
error = json.loads(body)["error"]
self.assertEqual(error["code"], "aiturk_transfer_failed")
self.assertEqual(error["type"], "invalid_request_error")
self.assertFalse(error["retryable"])
self.assertIn("görev eksik gönderilmedi", error["message"])
self.assertNotIn(b"response.failed", body)
self.assertEqual(req, original)
for model in (req["model"], "ide/another-provider"):
req["model"] = model
status, body, _ = self.iste("POST", "/backend-api/codex/responses", json.dumps(req).encode())
self.assertEqual(status, 200)
self.assertNotIn(b"aiturk_transfer_failed", body)
sent = json.loads(self.kopru.gelen[-1]["govde"])
wire = json.dumps(sent, ensure_ascii=False)
self.assertNotIn("gAAAA_synthetic_opaque", wire)
self.assertNotIn("encrypted_content", wire)
self.assertIn(gc.PEER_OMITTED, wire)
self.assertIn("VISIBLE_PEER_HEADER", wire)
for item in original["input"][:1] + original["input"][2:]:
self.assertIn(item, sent["input"])
self.assertEqual(req["input"], original["input"])
self.assertEqual(self.ozgun.gelen, [])
self.assertEqual(self.kopru.gelen, [])
self.assertEqual(bridge.TRANSFERS, {})
# Returning uses the client's original transcript, including opaque peer
# state. Projection never rewrites the persisted source or a native turn.
req["model"] = "gpt-6-astra"
raw = zstd(json.dumps(req).encode())
status, _, _ = self.iste("POST", "/backend-api/codex/responses", raw, {"Content-Encoding": "zstd"})
self.assertEqual(status, 200)
self.assertEqual(self.ozgun.gelen[-1]["govde"], raw)
def test_native_opaque_peer_history_remains_byte_identical_on_owner_route(self):
req = {"model": "gpt-6-astra", "input": [{"type": "agent_message", "author": "/root",
+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'
// 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 = '22'
export const CODEX_BRIDGE_VERSION = '23'
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'
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "aiturk-ide",
"productName": "AITURK IDE",
"private": true,
"version": "1.0.0-beta.44",
"version": "1.0.0-beta.45",
"description": "TurkServis resmi yapay zekâ geliştirme ortamı. Hermes Agent tabanlıdır.",
"author": "AITURK / TurkServis",
"repository": {
+1 -1
View File
@@ -65,7 +65,7 @@
},
"apps/desktop": {
"name": "aiturk-ide",
"version": "1.0.0-beta.44",
"version": "1.0.0-beta.45",
"license": "MIT",
"dependencies": {
"@assistant-ui/core": "0.2.23",