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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRDGW1LiKcDUZYUdKBoWYW
This commit is contained in:
1 parent
b560950f82
commit
1e0f2f9aea
8 files changed
+33
-39
No files matched your search
@@ -26,7 +26,7 @@ from resim_context import resimleri_sadelestir
|
|||||||
import ide_catalog
|
import ide_catalog
|
||||||
import ozet_kopru
|
import ozet_kopru
|
||||||
|
|
||||||
SURUM = "2"
|
SURUM = "3"
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
|
# Codex sends request bodies zstd-compressed. Python gained zstd in 3.14; for older
|
||||||
|
|||||||
@@ -127,11 +127,22 @@ def sec(data, tanimli, kapsam="ide"):
|
|||||||
gorunen = ts.get("display_name") or mid
|
gorunen = ts.get("display_name") or mid
|
||||||
kisa = gorunen.split(" · ")[-1].strip()
|
kisa = gorunen.split(" · ")[-1].strip()
|
||||||
combo = "/" not in kisa # saglayici/model bicimi degil: adlandirilmis combo ya da takma ad
|
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:
|
if kapsam != "hepsi" and not combo and kisa.lower() not in tanimli and mid.lower() not in tanimli:
|
||||||
continue
|
continue
|
||||||
gorulen.add(mid)
|
gorulen.add(mid)
|
||||||
secilen.append({"id": mid, "ad": gorunen, "sira": ts.get("order") or 0})
|
secilen.append({"id": mid, "ad": gorunen, "sira": ts.get("order") or 0})
|
||||||
secilen.sort(key=lambda s: (s["sira"], s["id"]))
|
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]
|
return [{"id": s["id"], "ad": s["ad"]} for s in secilen]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import json, os, tempfile, unittest
|
|||||||
import ide_catalog as c
|
import ide_catalog as c
|
||||||
|
|
||||||
|
|
||||||
def girdi(mid, ad, yetenek=("chat",), sira=0):
|
def girdi(mid, ad, yetenek=("chat",), sira=0, kaynak="omniroute"):
|
||||||
return {"id": mid, "turkservis": {"display_name": ad, "capabilities": list(yetenek), "order": sira}}
|
return {"id": mid, "turkservis": {"display_name": ad, "capabilities": list(yetenek), "order": sira, "source": kaynak}}
|
||||||
|
|
||||||
|
|
||||||
KATALOG = [
|
KATALOG = [
|
||||||
@@ -53,7 +53,19 @@ class Secim(unittest.TestCase):
|
|||||||
def test_ide_kapsami(self):
|
def test_ide_kapsami(self):
|
||||||
s = c.sec(KATALOG, ["cc/claude-opus-5-5"], "ide")
|
s = c.sec(KATALOG, ["cc/claude-opus-5-5"], "ide")
|
||||||
# Tanimli model + adlandirilmis combolar; tanimsiz saglayici modeli ve gorsel modeli yok.
|
# 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):
|
def test_gorunen_ad_yalniz_saglayici_ve_model(self):
|
||||||
for tam, beklenen in [("OmniRoute · ag/gemini-3.8-flash", "ag/gemini-3.8-flash"),
|
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):
|
def test_hepsi_kapsami_yalniz_sohbet(self):
|
||||||
s = c.sec(KATALOG, [], "hepsi")
|
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])
|
self.assertNotIn("gorsel", [x["id"] for x in s])
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
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 = '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_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_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
|
||||||
export const CODEX_LINK_RUN_VALUE = 'AITURK-IDE-Codex-Baglama'
|
export const CODEX_LINK_RUN_VALUE = 'AITURK-IDE-Codex-Baglama'
|
||||||
export const CODEX_LINK_LAUNCHER_NAME = 'baslat.pyw'
|
export const CODEX_LINK_LAUNCHER_NAME = 'baslat.pyw'
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ const PATHS: CodexLinkPaths = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BRIDGE_JSON = `${PATHS.bridgeHome}\\bridge.json`
|
const BRIDGE_JSON = `${PATHS.bridgeHome}\\bridge.json`
|
||||||
const BACKUP = `${PATHS.codexConfig}.aiturk-yedek`
|
const STATUS_OK = { ok: true, surum: '3', ide_durum: 'tamam', ide_model: 7, ozet: true }
|
||||||
const STATUS_OK = { ok: true, surum: '2', ide_durum: 'tamam', ide_model: 7, ozet: true }
|
|
||||||
const USER_CONFIG = 'model = "gpt-6"\r\n\r\n[features]\r\nweb = true\r\n'
|
const USER_CONFIG = 'model = "gpt-6"\r\n\r\n[features]\r\nweb = true\r\n'
|
||||||
|
|
||||||
interface World {
|
interface World {
|
||||||
@@ -73,12 +72,6 @@ function createWorld(patch: Partial<World> & { files?: Map<string, string> } = {
|
|||||||
world.events.push(`write ${file}`)
|
world.events.push(`write ${file}`)
|
||||||
world.files.set(file, text)
|
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) => {
|
spawnBridge: (command, args, env) => {
|
||||||
world.events.push(`spawn ${command} ${JSON.stringify(args)} ${JSON.stringify(env)}`)
|
world.events.push(`spawn ${command} ${JSON.stringify(args)} ${JSON.stringify(env)}`)
|
||||||
spawned = true
|
spawned = true
|
||||||
@@ -212,19 +205,17 @@ describe('applyCodexLink', () => {
|
|||||||
hermesHome: PATHS.hermesHome
|
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([
|
expect(world.events).toEqual([
|
||||||
`write ${BRIDGE_JSON}`,
|
`write ${BRIDGE_JSON}`,
|
||||||
`spawn ${PATHS.pythonw} ${JSON.stringify(args)} ${JSON.stringify(env)}`,
|
`spawn ${PATHS.pythonw} ${JSON.stringify(args)} ${JSON.stringify(env)}`,
|
||||||
'status ok',
|
'status ok',
|
||||||
`backup ${BACKUP}`,
|
|
||||||
`write ${PATHS.codexConfig}`,
|
`write ${PATHS.codexConfig}`,
|
||||||
'autostart set'
|
'autostart set'
|
||||||
])
|
])
|
||||||
|
|
||||||
expect(JSON.parse(world.files.get(BRIDGE_JSON) ?? '')).toEqual(codexBridgeConfig())
|
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(PATHS.codexConfig)).toBe(`${CODEX_LINK_LINE}\r\n${USER_CONFIG}`)
|
||||||
expect(world.files.get(BACKUP)).toBe(USER_CONFIG)
|
|
||||||
expect(world.autostart).toBe(
|
expect(world.autostart).toBe(
|
||||||
`"${PATHS.pythonw}" "${PATHS.bridgeScript}" --home "${PATHS.bridgeHome}" --hermes-home "${PATHS.hermesHome}"`
|
`"${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(status).toMatchObject({ enabled: true, bridgeRunning: false, lastError: 'bridge-start-failed' })
|
||||||
expect(spawns(world)).toHaveLength(1)
|
expect(spawns(world)).toHaveLength(1)
|
||||||
expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG)
|
expect(world.files.get(PATHS.codexConfig)).toBe(USER_CONFIG)
|
||||||
expect(world.files.has(BACKUP)).toBe(false)
|
|
||||||
expect(world.autostart).toBeNull()
|
expect(world.autostart).toBeNull()
|
||||||
expect(writes(world)).toEqual([`write ${BRIDGE_JSON}`])
|
expect(writes(world)).toEqual([`write ${BRIDGE_JSON}`])
|
||||||
// The start poll is bounded by its one budget.
|
// The start poll is bounded by its one budget.
|
||||||
@@ -289,15 +279,6 @@ describe('applyCodexLink', () => {
|
|||||||
expect(spawns(world)).toEqual([])
|
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 () => {
|
it('falls back to python.exe when pythonw.exe is missing', async () => {
|
||||||
const { link, world } = createWorld()
|
const { link, world } = createWorld()
|
||||||
world.files.delete(PATHS.pythonw)
|
world.files.delete(PATHS.pythonw)
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import path from 'node:path'
|
|||||||
import {
|
import {
|
||||||
addCodexLinkLine,
|
addCodexLinkLine,
|
||||||
CODEX_BRIDGE_VERSION,
|
CODEX_BRIDGE_VERSION,
|
||||||
CODEX_LINK_BACKUP_SUFFIX,
|
|
||||||
CODEX_LINK_LAUNCHER_NAME,
|
CODEX_LINK_LAUNCHER_NAME,
|
||||||
CODEX_LINK_REFRESH_URL,
|
CODEX_LINK_REFRESH_URL,
|
||||||
CODEX_LINK_RUN_KEY,
|
CODEX_LINK_RUN_KEY,
|
||||||
@@ -67,8 +66,6 @@ export interface CodexLinkDeps {
|
|||||||
readText: (file: string) => null | string
|
readText: (file: string) => null | string
|
||||||
/** tmp + rename; creates the parent folder. */
|
/** tmp + rename; creates the parent folder. */
|
||||||
writeTextAtomic: (file: string, text: string) => void
|
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. */
|
/** Start the bridge detached and hidden; it must outlive the IDE. */
|
||||||
spawnBridge: (command: string, args: string[], env: Record<string, string>) => void
|
spawnBridge: (command: string, args: string[], env: Record<string, string>) => void
|
||||||
/** Parsed JSON body, or null when nothing answered within `budgetMs`. */
|
/** Parsed JSON body, or null when nothing answered within `budgetMs`. */
|
||||||
@@ -334,7 +331,6 @@ export function createCodexLink(deps: CodexLinkDeps): CodexLink {
|
|||||||
|
|
||||||
if (edit.changed) {
|
if (edit.changed) {
|
||||||
try {
|
try {
|
||||||
deps.backupOnce(paths.codexConfig, `${paths.codexConfig}${CODEX_LINK_BACKUP_SUFFIX}`)
|
|
||||||
deps.writeTextAtomic(paths.codexConfig, edit.text)
|
deps.writeTextAtomic(paths.codexConfig, edit.text)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
fail('config-write-failed', error)
|
fail('config-write-failed', error)
|
||||||
@@ -472,11 +468,6 @@ export function createNodeCodexLinkDeps(paths: CodexLinkPaths, log: (message: st
|
|||||||
exists: file => fs.existsSync(file),
|
exists: file => fs.existsSync(file),
|
||||||
readText: readTextLossless,
|
readText: readTextLossless,
|
||||||
writeTextAtomic,
|
writeTextAtomic,
|
||||||
backupOnce: (source, target) => {
|
|
||||||
if (!fs.existsSync(target)) {
|
|
||||||
fs.copyFileSync(source, target, fs.constants.COPYFILE_EXCL)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
spawnBridge: (command, args, env) => {
|
spawnBridge: (command, args, env) => {
|
||||||
fs.mkdirSync(paths.bridgeHome, { recursive: true })
|
fs.mkdirSync(paths.bridgeHome, { recursive: true })
|
||||||
|
|
||||||
|
|||||||
@@ -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.21",
|
"version": "1.0.0-beta.22",
|
||||||
"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": {
|
||||||
|
|||||||
Generated
+1
-1
@@ -65,7 +65,7 @@
|
|||||||
},
|
},
|
||||||
"apps/desktop": {
|
"apps/desktop": {
|
||||||
"name": "aiturk-ide",
|
"name": "aiturk-ide",
|
||||||
"version": "1.0.0-beta.21",
|
"version": "1.0.0-beta.22",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@assistant-ui/core": "0.2.23",
|
"@assistant-ui/core": "0.2.23",
|
||||||
|
|||||||
Reference in new issue
Block a user