Compare commits

...
Author SHA1 Message Date
yilsemandClaude Opus 5.5 88e660768c feat(codex-bridge): keep the gateway's short "9R" mark in Codex model names
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRDGW1LiKcDUZYUdKBoWYW
2026-10-09 19:27:22 +03:00
yilsemandClaude Opus 5.5 bfd0ff196d feat(codex-bridge): add the models of 9Router's providers to Codex (beta.23)
Codex only received combos and the models named in the IDE config, so none of
the models behind the providers added to 9Router showed up. Those single
models are now always added; 9Router's combos stay hidden. The copy of the
same model that comes through OmniRoute is still added only when the IDE
config names it, so each model is listed once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRDGW1LiKcDUZYUdKBoWYW
2026-10-09 19:26:00 +03:00
yilsemandClaude Opus 5.5 1e0f2f9aea 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
2026-10-09 17:51:10 +03:00
8 changed files with 67 additions and 44 deletions

No files matched your search

+1 -1
View File
@@ -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 = "4"
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
@@ -6,7 +6,8 @@ anahtar IDE'nin kendi .env dosyasindan her yenilemede okunur, bridge.json'a yazi
Ac/kapat: %LOCALAPPDATA%\\TurkServis\\AITURK-IDE\\codex-baglama.json Ac/kapat: %LOCALAPPDATA%\\TurkServis\\AITURK-IDE\\codex-baglama.json
{"enabled": true, "kapsam": "ide"} {"enabled": true, "kapsam": "ide"}
Dosya yoksa ACIK sayilir (kullanici karari, 9 Ekim 2026: varsayilan acik). Dosya yoksa ACIK sayilir (kullanici karari, 9 Ekim 2026: varsayilan acik).
kapsam "ide" : IDE ayarinda tanimli modeller + adlandirilmis combolar (varsayilan) kapsam "ide" : IDE ayarinda tanimli modeller + 9Router'a eklenmis saglayicilarin modelleri +
adlandirilmis combolar (varsayilan)
kapsam "hepsi" : gecitteki butun sohbet modelleri (1000+; Codex listesi cok buyur) kapsam "hepsi" : gecitteki butun sohbet modelleri (1000+; Codex listesi cok buyur)
""" """
import json, os, re, urllib.request import json, os, re, urllib.request
@@ -84,23 +85,33 @@ def ide_yapilandirma(agent=AGENT):
return base.rstrip("/"), varsayilan, adlar return base.rstrip("/"), varsayilan, adlar
DOKUZ_ISARET = "9R · "
def _isaret(ad):
"""Gecidin dogrudan 9Router'dan gelen tekil modele koydugu kisa isaret korunur (kullanici karari,
9 Ekim 2026: "9Router" yazisi uzun, hic yazmamak da sorun; "9R" olarak kisa gorunsun)."""
return DOKUZ_ISARET if (ad or "").startswith(DOKUZ_ISARET) else ""
def kisa_ad(ad): def kisa_ad(ad):
"""Codex secicisinde gorunecek ad: yalniz kullanicinin ekledigi saglayici + model adi """Codex secicisinde gorunecek ad: yalniz kullanicinin ekledigi saglayici + model adi
(kullanici istegi, 9 Ekim 2026). "OmniRoute · " / "9Router · " gibi gecit onekleri ve aradaki (kullanici istegi, 9 Ekim 2026). "OmniRoute · " / "9Router · " gibi gecit onekleri ve aradaki
saglayici halkalari gosterilmez: "nano/qwen/qwen3.8-27b" -> "nano/qwen3.8-27b", saglayici halkalari gosterilmez: "nano/qwen/qwen3.8-27b" -> "nano/qwen3.8-27b",
"OmniRoute · ag/gemini-3.8-flash" -> "ag/gemini-3.8-flash", combo adi oldugu gibi kalir.""" "OmniRoute · ag/gemini-3.8-flash" -> "ag/gemini-3.8-flash", combo adi oldugu gibi kalir."""
on = _isaret(ad)
ad = (ad or "").split(" · ")[-1].strip() ad = (ad or "").split(" · ")[-1].strip()
parcalar = [p for p in ad.split("/") if p] parcalar = [p for p in ad.split("/") if p]
if len(parcalar) <= 2: if len(parcalar) <= 2:
return "/".join(parcalar) or ad return on + ("/".join(parcalar) or ad)
return parcalar[0] + "/" + parcalar[-1] return on + parcalar[0] + "/" + parcalar[-1]
def adlandir(girdiler, dolu=()): def adlandir(girdiler, dolu=()):
"""[(kimlik, tam_ad)] -> {kimlik: gorunen_ad}. Kisa adlar cakisirsa aradaki halkalar geri konur; """[(kimlik, tam_ad)] -> {kimlik: gorunen_ad}. Kisa adlar cakisirsa aradaki halkalar geri konur;
yine cakisirsa kimligin sonu eklenir. `dolu`: baska kaynaktan gelen, zaten kullanilan adlar.""" yine cakisirsa kimligin sonu eklenir. `dolu`: baska kaynaktan gelen, zaten kullanilan adlar."""
kisa = {k: kisa_ad(a) for k, a in girdiler} kisa = {k: kisa_ad(a) for k, a in girdiler}
tam = {k: (a or "").split(" · ")[-1].strip() for k, a in girdiler} tam = {k: _isaret(a) + (a or "").split(" · ")[-1].strip() for k, a in girdiler}
sayac = {} sayac = {}
for v in list(kisa.values()) + list(dolu): for v in list(kisa.values()) + list(dolu):
sayac[v.lower()] = sayac.get(v.lower(), 0) + 1 sayac[v.lower()] = sayac.get(v.lower(), 0) + 1
@@ -127,11 +138,26 @@ 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
if kapsam != "hepsi" and not combo and kisa.lower() not in tanimli and mid.lower() not in tanimli: # 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
# 9Router'a eklenmis saglayicilarin tekil modelleri her zaman eklenir (kullanici karari, 9 Ekim 2026:
# "9Router'in combosu olmasin dedim, 9Router'in modeli olsun"). Ayni modelin OmniRoute uzerinden
# gelen kopyasi yalniz IDE ayarinda tanimliysa eklenir; boylece her model listede bir kez durur.
dokuz_model = not combo and ts.get("source") == "9router"
if kapsam != "hepsi" and not combo and not dokuz_model 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,13 +53,39 @@ 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_9router_saglayici_modelleri_tanimsiz_da_olsa_eklenir(self):
katalog = [girdi("9router-nano-x-aaaa1111", "nano/x", kaynak="9router", sira=1),
girdi("9router-nano-x", "nano/x (2)", sira=2),
girdi("9router-ocg-y-bbbb2222", "ocg/y", kaynak="9router", sira=3),
girdi("vag-z", "vag/z", sira=4),
girdi("9router-ses", "nano/ses", ("tts",), kaynak="9router", sira=5),
girdi("9router-Takim-Agir-6e65dba5", "Takim-Agir", kaynak="9router", sira=6)]
s = c.sec(katalog, [], "ide")
# 9Router modelleri IDE ayarinda adi gecmese de gelir; OmniRoute kopyasi, OmniRoute'un kendi
# saglayici modeli, sohbet olmayan model ve 9Router combosu gelmez.
self.assertEqual([(x["id"], x["ad"]) for x in s],
[("9router-nano-x-aaaa1111", "nano/x"), ("9router-ocg-y-bbbb2222", "ocg/y")])
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"),
("nano/qwen/qwen3.8-27b-obliterated:thinking", "nano/qwen3.8-27b-obliterated:thinking"), ("nano/qwen/qwen3.8-27b-obliterated:thinking", "nano/qwen3.8-27b-obliterated:thinking"),
("9Router · Nous-Research/inclusionai/ling-3.1-flash", "Nous-Research/ling-3.1-flash"), ("9Router · Nous-Research/inclusionai/ling-3.1-flash", "Nous-Research/ling-3.1-flash"),
("cf/@cf/qwen/qwq-32b", "cf/qwq-32b"), ("cf/@cf/qwen/qwq-32b", "cf/qwq-32b"),
("9R · nano/qwen/qwen3.8-27b", "9R · nano/qwen3.8-27b"), ("9R · ag/gemini", "9R · ag/gemini"),
("OmniRoute · Ajan-Arac", "Ajan-Arac"), ("cc/claude-opus-5-5", "cc/claude-opus-5-5")]: ("OmniRoute · Ajan-Arac", "Ajan-Arac"), ("cc/claude-opus-5-5", "cc/claude-opus-5-5")]:
self.assertEqual(c.kisa_ad(tam), beklenen, tam) self.assertEqual(c.kisa_ad(tam), beklenen, tam)
@@ -74,7 +100,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])
+1 -2
View File
@@ -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 = '4'
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'
+2 -21
View File
@@ -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: '4', 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)
-9
View File
@@ -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 })
+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.21", "version": "1.0.0-beta.23",
"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.21", "version": "1.0.0-beta.23",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@assistant-ui/core": "0.2.23", "@assistant-ui/core": "0.2.23",