feat(codex-bridge): handoff instruction for Codex Desktop only (off by default)
User decision: the "larger work goes to AITURK" instruction lives only in what Codex IDE sees; the CLI must not know it exists. The bridge adds it in flight by the originator header (measured: Codex Desktop, codex-tui, codex_exec) and only when a send command is configured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d9a7661281
commit
1445e204da
6 files changed
+108
-2
No files matched your search
@@ -45,6 +45,13 @@ for the chat, i.e. until the user picks another one in the list.
|
||||
|
||||
Setting: `sesle_model` (default on).
|
||||
|
||||
### Work handoff instruction (prepared, off by default)
|
||||
|
||||
`is_hatti.py`: when `aiturk_hat` is enabled and has a `komut`, the bridge prepends a developer message ("small work
|
||||
yourself; larger work goes to AITURK via this command") to requests whose `originator` header is `Codex Desktop`.
|
||||
The Codex CLI (`codex-tui`, `codex_exec`) never receives it and nothing is written to any file it reads. Each new
|
||||
`originator` value is logged once (`istemci goruldu`).
|
||||
|
||||
## Version 7 (IDE beta.26): external models behave like subscription models
|
||||
|
||||
Measured on 10 Oct 2026 with the unmodified Codex binary (`codex exec`, same conversation, model switched per turn).
|
||||
|
||||
@@ -28,6 +28,7 @@ import ozet_kopru
|
||||
import pencere
|
||||
import yarim
|
||||
import model_gecis
|
||||
import is_hatti
|
||||
|
||||
SURUM = "8"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -666,6 +667,7 @@ def decompress(body, enc):
|
||||
|
||||
OZET_FRENI = ozet_kopru.DonguFreni()
|
||||
YONLENDIRME = model_gecis.Yonlendirme(HOME)
|
||||
GORULEN_ISTEMCI = set() # originator degerleri; her biri gunluge bir kez yazilir
|
||||
_BILINEN = {"kaynak": None, "liste": []}
|
||||
|
||||
def bilinen_modeller():
|
||||
@@ -1108,6 +1110,16 @@ class H(http.server.BaseHTTPRequestHandler):
|
||||
# istek sanip is yapmaz, yavas modelde asil soru beklemez. Secilen modelin adiyla aninda donulur.
|
||||
log(f"[{n} {slug}] nokta mesaji kopruden karsilandi (modele gonderilmedi)")
|
||||
return self._yerel_yanit(gelen, f"{model_adi(slug)} hazır. Sohbetin modeli artık {model_adi(slug)}.")
|
||||
# Is hatti talimati yalniz masaustu uygulamasindan gelen istege eklenir; CLI hic gormez (is_hatti.py).
|
||||
kaynak = self.headers.get("originator") or ""
|
||||
if kaynak and kaynak not in GORULEN_ISTEMCI:
|
||||
GORULEN_ISTEMCI.add(kaynak)
|
||||
log(f"istemci goruldu: {kaynak}")
|
||||
try:
|
||||
if is_hatti.masaustu_mu(kaynak) and is_hatti.ekle(req, is_hatti.talimat(CFG.get("aiturk_hat"))):
|
||||
govde_degisti = True
|
||||
except Exception as e:
|
||||
log(f"[{n} {slug}] is hatti talimati atlandi: {type(e).__name__}: {e}")
|
||||
# Ayni sohbetin onceki cevabi yarida kesildiyse (yarim.py) metni bu istege not olarak eklenir.
|
||||
yarim_eklenen = 0
|
||||
if CFG.get("yarim_cevap", True):
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Codex IDE'ye "is kucuk degilse AITURK yapacak" talimati (11 Ekim 2026).
|
||||
|
||||
Kullanici karari: talimat yalniz Codex IDE'nin (masaustu uygulamasi) gordugu sistem talimatinda durur; Codex CLI
|
||||
talimati hic gormez, varligindan haberi olmaz. Karari secili model verir; kucuk modellerin yanilmasi kabul edilir,
|
||||
ayri bir yazilimla onune gecilmez.
|
||||
|
||||
Bu yuzden talimat hicbir dosyaya yazilmaz (AGENTS.md'yi CLI da okur): kopru, yalniz masaustu uygulamasindan gelen
|
||||
isteklere, istek gecerken ekler. Istemci `originator` basligindan ayirt edilir (olcum 11 Ekim 2026: masaustu
|
||||
"Codex Desktop", etkilesimli CLI "codex-tui", `codex exec` "codex_exec").
|
||||
|
||||
Ayar (bridge.json): "aiturk_hat": {"enabled": true, "komut": "<isi AITURK'e gonderen komut>", "talimat": "<istege bagli>"}.
|
||||
Komut tanimli degilse talimat eklenmez: gonderme yolu olmayan bir "AITURK'e gonder" talimati modeli yaniltir.
|
||||
"""
|
||||
|
||||
MASAUSTU = {"codex desktop"}
|
||||
ISARET = "[AITURK iş hattı]"
|
||||
VARSAYILAN = (
|
||||
ISARET + " Bu Codex, AITURK'e bağlı. Şu kurala göre çalış:\n"
|
||||
"1. Basit soruyu, bilgi isteğini ya da tek küçük düzeltmeyi kendin yap.\n"
|
||||
"2. Kullanıcı \"sen yap\" derse işi kendin yap.\n"
|
||||
"3. İş küçük değilse (birden çok dosya, birkaç adım, yeni özellik, araştırma gerektiren iş) kendin yapma; "
|
||||
"işi şu komutla AITURK'e gönder, bitmesini bekle ve sonucu kullanıcıya kısaca aktar:\n{komut}\n"
|
||||
"Emin değilsen işi küçük say ve kendin yap."
|
||||
)
|
||||
|
||||
|
||||
def masaustu_mu(originator):
|
||||
return str(originator or "").strip().lower() in MASAUSTU
|
||||
|
||||
|
||||
def talimat(ayar):
|
||||
"""Eklenecek metin; hat kapaliysa ya da komut yoksa None."""
|
||||
if not isinstance(ayar, dict) or not ayar.get("enabled"):
|
||||
return None
|
||||
komut = str(ayar.get("komut") or "").strip()
|
||||
if not komut:
|
||||
return None
|
||||
return str(ayar.get("talimat") or VARSAYILAN).replace("{komut}", komut)
|
||||
|
||||
|
||||
def ekle(req, metin):
|
||||
"""Talimati istegin basina developer mesaji olarak koyar. True: eklendi (zaten varsa eklenmez)."""
|
||||
girdi = req.get("input")
|
||||
if not metin or not isinstance(girdi, list):
|
||||
return False
|
||||
for oge in girdi[:3]:
|
||||
icerik = oge.get("content") if isinstance(oge, dict) else None
|
||||
yazi = icerik if isinstance(icerik, str) else " ".join(str(p.get("text") or "") for p in icerik or [] if isinstance(p, dict))
|
||||
if ISARET in yazi:
|
||||
return False
|
||||
girdi.insert(0, {"type": "message", "role": "developer", "content": [{"type": "input_text", "text": metin}]})
|
||||
return True
|
||||
@@ -31,7 +31,7 @@ class AccessPrograms(unittest.TestCase):
|
||||
"fix_tool_ids": lambda req: 0,
|
||||
"drop_foreign_items": lambda req: 0,
|
||||
"SseRewriter": Mock(),
|
||||
"log": lambda message: None, "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
||||
"log": lambda message: None, "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
||||
"DEBUG": False, "UA": "test", "RETRIES": 1, "UPSTREAM_IDLE_TIMEOUT": 3900,
|
||||
}
|
||||
exec(compile(ast.Module(body=[flatten, claude_sonu, responses], type_ignores=[]), "bridge.py", "exec"), self.env)
|
||||
|
||||
@@ -40,7 +40,7 @@ class ComboNotResent(unittest.TestCase):
|
||||
"fix_tool_ids": lambda req: 0, "drop_foreign_items": lambda req: 0,
|
||||
"SseRewriter": Mock(), "log": lambda message: None,
|
||||
"DEBUG": False, "UA": "test", "RETRIES": 6, "UPSTREAM_IDLE_TIMEOUT": 3600,
|
||||
"NO_RETRY_PATTERNS": [], "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
||||
"NO_RETRY_PATTERNS": [], "pencere": __import__("pencere"), "IDE_BILGI": {}, "yarim": __import__("yarim"), "is_hatti": __import__("is_hatti"), "GORULEN_ISTEMCI": set(), "nokta_mesaji_mi": lambda req: False, "EXTRA_MODELS": [],
|
||||
}
|
||||
exec(compile(ast.Module(body=[pick("flatten_request"), pick("claude_sonu_duzelt"), pick("is_timeout"), responses],
|
||||
type_ignores=[]), "bridge.py", "exec"), self.env)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
"""The "send larger work to AITURK" instruction reaches only Codex Desktop, and only when a send command exists."""
|
||||
import unittest
|
||||
|
||||
import is_hatti as h
|
||||
|
||||
|
||||
class Hat(unittest.TestCase):
|
||||
def test_only_the_desktop_app_is_recognised(self):
|
||||
self.assertTrue(h.masaustu_mu("Codex Desktop"))
|
||||
for other in ("codex-tui", "codex_exec", "codex_cli_rs", "", None, "Codex Desktop Helper"):
|
||||
self.assertFalse(h.masaustu_mu(other), other)
|
||||
|
||||
def test_no_instruction_without_the_switch_or_without_a_send_command(self):
|
||||
self.assertIsNone(h.talimat(None))
|
||||
self.assertIsNone(h.talimat({"enabled": False, "komut": "aiturk gonder"}))
|
||||
self.assertIsNone(h.talimat({"enabled": True}))
|
||||
self.assertIsNone(h.talimat({"enabled": True, "komut": " "}))
|
||||
text = h.talimat({"enabled": True, "komut": "aiturk gonder --dosya is.md"})
|
||||
self.assertIn("aiturk gonder --dosya is.md", text)
|
||||
self.assertIn("sen yap", text)
|
||||
self.assertEqual(h.talimat({"enabled": True, "komut": "x", "talimat": "ozel {komut}"}), "ozel x")
|
||||
|
||||
def test_instruction_is_put_first_once_and_never_repeated(self):
|
||||
req = {"input": [{"type": "message", "role": "user", "content": "selam"}]}
|
||||
text = h.talimat({"enabled": True, "komut": "aiturk gonder"})
|
||||
self.assertTrue(h.ekle(req, text))
|
||||
self.assertEqual((req["input"][0]["role"], len(req["input"])), ("developer", 2))
|
||||
self.assertFalse(h.ekle(req, text))
|
||||
self.assertEqual(len(req["input"]), 2)
|
||||
self.assertFalse(h.ekle({"input": "duz metin"}, text))
|
||||
self.assertFalse(h.ekle(req, None))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user