From 1e0f2f9aea28d5e39cf63a091eb5c588e0792ae9 Mon Sep 17 00:00:00 2001 From: yilsem Date: Fri, 9 Oct 2026 17:51:10 +0300 Subject: [PATCH] feat(codex-bridge): hide 9Router combos in Codex, drop the config backup (beta.22) Combos that come from 9Router, directly or through the gateway's 9router provider, are no longer added to Codex's model list; the gateway's own combos keep their plain names. The IDE no longer leaves a config.toml backup behind: turning the setting off already restores the file byte for byte. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HRDGW1LiKcDUZYUdKBoWYW --- apps/desktop/assets/codex-bridge/bridge.py | 2 +- .../assets/codex-bridge/ide_catalog.py | 11 +++++++++ .../assets/codex-bridge/test_ide_catalog.py | 20 ++++++++++++---- apps/desktop/electron/codex-link-config.ts | 3 +-- apps/desktop/electron/codex-link.test.ts | 23 ++----------------- apps/desktop/electron/codex-link.ts | 9 -------- apps/desktop/package.json | 2 +- package-lock.json | 2 +- 8 files changed, 33 insertions(+), 39 deletions(-) diff --git a/apps/desktop/assets/codex-bridge/bridge.py b/apps/desktop/assets/codex-bridge/bridge.py index 2be3604..8117502 100644 --- a/apps/desktop/assets/codex-bridge/bridge.py +++ b/apps/desktop/assets/codex-bridge/bridge.py @@ -26,7 +26,7 @@ from resim_context import resimleri_sadelestir import ide_catalog import ozet_kopru -SURUM = "2" +SURUM = "3" HERE = os.path.dirname(os.path.abspath(__file__)) # Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older diff --git a/apps/desktop/assets/codex-bridge/ide_catalog.py b/apps/desktop/assets/codex-bridge/ide_catalog.py index 0f64b26..e3fdb21 100644 --- a/apps/desktop/assets/codex-bridge/ide_catalog.py +++ b/apps/desktop/assets/codex-bridge/ide_catalog.py @@ -127,11 +127,22 @@ def sec(data, tanimli, kapsam="ide"): gorunen = ts.get("display_name") or mid kisa = gorunen.split(" · ")[-1].strip() combo = "/" not in kisa # saglayici/model bicimi degil: adlandirilmis combo ya da takma ad + # 9Router'dan gelen combolar Codex'te gosterilmez (kullanici karari, 9 Ekim 2026): dogrudan + # 9Router kaynagi ve gecidin "9router" saglayicisi uzerinden gelen kopyalari. + if combo and (ts.get("source") == "9router" or mid.lower().startswith("9router-")): + continue if kapsam != "hepsi" and not combo and kisa.lower() not in tanimli and mid.lower() not in tanimli: continue gorulen.add(mid) secilen.append({"id": mid, "ad": gorunen, "sira": ts.get("order") or 0}) secilen.sort(key=lambda s: (s["sira"], s["id"])) + # 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"]} for s in secilen] diff --git a/apps/desktop/assets/codex-bridge/test_ide_catalog.py b/apps/desktop/assets/codex-bridge/test_ide_catalog.py index 9c2de87..d445422 100644 --- a/apps/desktop/assets/codex-bridge/test_ide_catalog.py +++ b/apps/desktop/assets/codex-bridge/test_ide_catalog.py @@ -2,8 +2,8 @@ import json, os, tempfile, unittest import ide_catalog as c -def girdi(mid, ad, yetenek=("chat",), sira=0): - return {"id": mid, "turkservis": {"display_name": ad, "capabilities": list(yetenek), "order": sira}} +def girdi(mid, ad, yetenek=("chat",), sira=0, kaynak="omniroute"): + return {"id": mid, "turkservis": {"display_name": ad, "capabilities": list(yetenek), "order": sira, "source": kaynak}} KATALOG = [ @@ -53,7 +53,19 @@ class Secim(unittest.TestCase): def test_ide_kapsami(self): s = c.sec(KATALOG, ["cc/claude-opus-5-5"], "ide") # Tanimli model + adlandirilmis combolar; tanimsiz saglayici modeli ve gorsel modeli yok. - 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-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), + 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. 9Router'in tekil modeli (combo degil) etkilenmez. + self.assertEqual([(x["id"], x["ad"]) for x in s], + [("Takim-Agir", "Takim-Agir"), ("Ajan-Arac", "Ajan-Arac"), ("9router-nano-x", "nano/x")]) def test_gorunen_ad_yalniz_saglayici_ve_model(self): for tam, beklenen in [("OmniRoute · ag/gemini-3.8-flash", "ag/gemini-3.8-flash"), @@ -74,7 +86,7 @@ class Secim(unittest.TestCase): def test_hepsi_kapsami_yalniz_sohbet(self): 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]) diff --git a/apps/desktop/electron/codex-link-config.ts b/apps/desktop/electron/codex-link-config.ts index 53ae738..7831cd7 100644 --- a/apps/desktop/electron/codex-link-config.ts +++ b/apps/desktop/electron/codex-link-config.ts @@ -27,10 +27,9 @@ 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 = '2' +export const CODEX_BRIDGE_VERSION = '3' 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_VALUE = 'AITURK-IDE-Codex-Baglama' export const CODEX_LINK_LAUNCHER_NAME = 'baslat.pyw' diff --git a/apps/desktop/electron/codex-link.test.ts b/apps/desktop/electron/codex-link.test.ts index f757559..3fa0181 100644 --- a/apps/desktop/electron/codex-link.test.ts +++ b/apps/desktop/electron/codex-link.test.ts @@ -21,8 +21,7 @@ const PATHS: CodexLinkPaths = { } const BRIDGE_JSON = `${PATHS.bridgeHome}\\bridge.json` -const BACKUP = `${PATHS.codexConfig}.aiturk-yedek` -const STATUS_OK = { ok: true, surum: '2', ide_durum: 'tamam', ide_model: 7, ozet: true } +const STATUS_OK = { ok: true, surum: '3', ide_durum: 'tamam', ide_model: 7, ozet: true } const USER_CONFIG = 'model = "gpt-6"\r\n\r\n[features]\r\nweb = true\r\n' interface World { @@ -73,12 +72,6 @@ function createWorld(patch: Partial & { files?: Map } = { world.events.push(`write ${file}`) 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) => { world.events.push(`spawn ${command} ${JSON.stringify(args)} ${JSON.stringify(env)}`) spawned = true @@ -212,19 +205,17 @@ describe('applyCodexLink', () => { 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([ `write ${BRIDGE_JSON}`, `spawn ${PATHS.pythonw} ${JSON.stringify(args)} ${JSON.stringify(env)}`, 'status ok', - `backup ${BACKUP}`, `write ${PATHS.codexConfig}`, 'autostart set' ]) 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(BACKUP)).toBe(USER_CONFIG) expect(world.autostart).toBe( `"${PATHS.pythonw}" "${PATHS.bridgeScript}" --home "${PATHS.bridgeHome}" --hermes-home "${PATHS.hermesHome}"` ) @@ -238,7 +229,6 @@ describe('applyCodexLink', () => { expect(status).toMatchObject({ enabled: true, bridgeRunning: false, lastError: 'bridge-start-failed' }) expect(spawns(world)).toHaveLength(1) expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG) - expect(world.files.has(BACKUP)).toBe(false) expect(world.autostart).toBeNull() expect(writes(world)).toEqual([`write ${BRIDGE_JSON}`]) // The start poll is bounded by its one budget. @@ -289,15 +279,6 @@ describe('applyCodexLink', () => { expect(spawns(world)).toEqual([]) }) - it('never overwrites the first backup', async () => { - const { link, world } = createWorld({ listening: true }) - world.files.set(BACKUP, 'the original original') - - await link.apply() - - expect(world.files.get(BACKUP)).toBe('the original original') - }) - it('falls back to python.exe when pythonw.exe is missing', async () => { const { link, world } = createWorld() world.files.delete(PATHS.pythonw) diff --git a/apps/desktop/electron/codex-link.ts b/apps/desktop/electron/codex-link.ts index a8081f4..c5d2081 100644 --- a/apps/desktop/electron/codex-link.ts +++ b/apps/desktop/electron/codex-link.ts @@ -23,7 +23,6 @@ import path from 'node:path' import { addCodexLinkLine, CODEX_BRIDGE_VERSION, - CODEX_LINK_BACKUP_SUFFIX, CODEX_LINK_LAUNCHER_NAME, CODEX_LINK_REFRESH_URL, CODEX_LINK_RUN_KEY, @@ -67,8 +66,6 @@ export interface CodexLinkDeps { readText: (file: string) => null | string /** tmp + rename; creates the parent folder. */ 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. */ spawnBridge: (command: string, args: string[], env: Record) => void /** Parsed JSON body, or null when nothing answered within `budgetMs`. */ @@ -334,7 +331,6 @@ export function createCodexLink(deps: CodexLinkDeps): CodexLink { if (edit.changed) { try { - deps.backupOnce(paths.codexConfig, `${paths.codexConfig}${CODEX_LINK_BACKUP_SUFFIX}`) deps.writeTextAtomic(paths.codexConfig, edit.text) } catch (error) { fail('config-write-failed', error) @@ -472,11 +468,6 @@ export function createNodeCodexLinkDeps(paths: CodexLinkPaths, log: (message: st exists: file => fs.existsSync(file), readText: readTextLossless, writeTextAtomic, - backupOnce: (source, target) => { - if (!fs.existsSync(target)) { - fs.copyFileSync(source, target, fs.constants.COPYFILE_EXCL) - } - }, spawnBridge: (command, args, env) => { fs.mkdirSync(paths.bridgeHome, { recursive: true }) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 10b885d..d157162 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.21", + "version": "1.0.0-beta.22", "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 3b34def..ddb897c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -65,7 +65,7 @@ }, "apps/desktop": { "name": "aiturk-ide", - "version": "1.0.0-beta.21", + "version": "1.0.0-beta.22", "license": "MIT", "dependencies": { "@assistant-ui/core": "0.2.23",