Menggabungkan History Chat Codex Desktop Saat Berpindah dari OpenAI ke 9Router

 Cara Menampilkan Kembali History Chat Codex Desktop Setelah Pindah ke 9Router, Saat mencoba memakai 9Router di Codex Desktop / Codex App, saya menemukan satu masalah: setelah config 9Router diterapkan, history chat lama yang dibuat saat login ChatGPT/OpenAI terlihat hilang.

Awalnya saya kira chat lama benar-benar terhapus. Ternyata tidak. Chat tersebut masih ada di komputer, hanya saja Codex menampilkan history berdasarkan model_provider yang sedang aktif.

Masalahnya

Sebelum memakai 9Router, config Codex saya tidak memiliki baris ini:

model_provider = "9router"

Setelah apply config dari 9Router, config berubah menjadi seperti ini:

model = "conding-combo"
model_provider = "9router"

[model_providers.9router]
name = "9Router"
base_url = "http://127.0.0.1:20128/v1"
wire_api = "responses"

Efeknya:

Saat provider OpenAI aktif:
- chat OpenAI muncul
- chat 9Router tidak muncul

Saat provider 9Router aktif:
- chat 9Router muncul
- chat OpenAI tidak muncul

Jadi sebenarnya chat tidak hilang, tetapi terpisah berdasarkan provider.

Lokasi Data Codex

Di macOS, data Codex ada di:

~/.codex

Database history-nya ada di:

~/.codex/state_5.sqlite

Di database tersebut ada tabel threads, dan salah satu kolom pentingnya adalah:

model_provider

Pada kasus saya, hasil pengecekan menunjukkan:

TABLE: threads
  'openai': 31
  '9router': 3

Artinya ada 31 chat lama yang masih berlabel openai, dan 3 chat baru yang berlabel 9router.

Tujuan

Karena saya ingin menggunakan 9Router sebagai provider utama, maka saya mengubah label history lama dari:

openai

menjadi:

9router

Supaya semua chat tampil ketika Codex memakai config 9Router.

Peringatan

Cara ini termasuk hack lokal. Lakukan dengan hati-hati.

Sebelum mengubah apa pun, tutup Codex terlebih dahulu dan backup folder .codex.

osascript -e 'quit app "Codex"'
pkill -f Codex
pkill -f codex

Backup folder Codex:

cp -a ~/.codex ~/.codex.backup-before-provider-merge-$(date +%Y%m%d-%H%M%S)

Jika muncul pesan seperti ini:

fsmonitor--daemon.ipc is a socket (not copied)

itu aman diabaikan. File tersebut adalah socket/runtime, bukan data chat utama.

Cek Isi Database

Jalankan perintah ini untuk melihat provider yang tersimpan di database:

python3 - <<'PY'
import sqlite3
from pathlib import Path

db = Path.home() / ".codex" / "state_5.sqlite"

print("DB:", db)
print("exists:", db.exists())

conn = sqlite3.connect(db)
cur = conn.cursor()

cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = [r[0] for r in cur.fetchall()]

for table in tables:
    cur.execute(f"PRAGMA table_info({table})")
    cols = [r[1] for r in cur.fetchall()]

    if "model_provider" in cols:
        print("\nTABLE:", table)
        cur.execute(f"""
            SELECT model_provider, COUNT(*)
            FROM {table}
            GROUP BY model_provider
            ORDER BY COUNT(*) DESC
        """)
        for provider, count in cur.fetchall():
            print(f"  {provider!r}: {count}")

conn.close()
PY

Contoh hasil:

TABLE: threads
  'openai': 31
  '9router': 3

Patch Provider dari OpenAI ke 9Router

Setelah yakin datanya benar, jalankan script berikut:

python3 - <<'PY'
import sqlite3
import shutil
from pathlib import Path
from datetime import datetime

codex = Path.home() / ".codex"
db = codex / "state_5.sqlite"

backup_dir = codex / f"provider-force-merge-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
backup_dir.mkdir(parents=True, exist_ok=True)

backup_db = backup_dir / "state_5.sqlite.bak"
shutil.copy2(db, backup_db)

print("Backup DB:", backup_db)

conn = sqlite3.connect(db)
cur = conn.cursor()

print("\nSebelum:")
cur.execute("""
SELECT model_provider, archived, thread_source, cwd, COUNT(*)
FROM threads
GROUP BY model_provider, archived, thread_source, cwd
ORDER BY model_provider, archived, cwd
""")
for row in cur.fetchall():
    print(row)

# Ubah thread user dari OpenAI menjadi 9Router
cur.execute("""
UPDATE threads
SET model_provider = '9router'
WHERE model_provider = 'openai'
  AND (thread_source = 'user' OR thread_source IS NULL)
""")
print("\nOpenAI user/null -> 9router:", cur.rowcount)

# Samakan nama model agar sesuai config 9Router
cur.execute("""
UPDATE threads
SET model = 'conding-combo'
WHERE model_provider = '9router'
  AND (thread_source = 'user' OR thread_source IS NULL)
""")
print("Set model conding-combo:", cur.rowcount)

# Buka arsip thread user 9Router agar muncul di history
cur.execute("""
UPDATE threads
SET archived = 0, archived_at = NULL
WHERE model_provider = '9router'
  AND (thread_source = 'user' OR thread_source IS NULL)
""")
print("Unarchive user 9router:", cur.rowcount)

conn.commit()

try:
    cur.execute("PRAGMA wal_checkpoint(FULL)")
    print("WAL checkpoint:", cur.fetchall())
except Exception as e:
    print("WAL checkpoint skipped:", e)

print("\nSesudah:")
cur.execute("""
SELECT model_provider, archived, thread_source, cwd, COUNT(*)
FROM threads
GROUP BY model_provider, archived, thread_source, cwd
ORDER BY model_provider, archived, cwd
""")
for row in cur.fetchall():
    print(row)

conn.close()
PY

Setelah itu buka kembali Codex App.

Hasil

Setelah patch berhasil, chat lama OpenAI dan chat baru 9Router muncul bersamaan saat config Codex memakai:

model = "conding-combo"
model_provider = "9router"

[model_providers.9router]
name = "9Router"
base_url = "http://127.0.0.1:20128/v1"
wire_api = "responses"

Cara Restore Jika Gagal

Script tadi otomatis membuat backup database di folder seperti ini:

~/.codex/provider-force-merge-YYYYMMDD-HHMMSS/state_5.sqlite.bak

Untuk restore:

cp ~/.codex/provider-force-merge-YYYYMMDD-HHMMSS/state_5.sqlite.bak ~/.codex/state_5.sqlite

Ganti YYYYMMDD-HHMMSS sesuai nama folder backup yang muncul di terminal.

Kesimpulan

Chat Codex Desktop sebenarnya tidak hilang saat berpindah dari OpenAI ke 9Router. History hanya difilter berdasarkan model_provider.

Solusinya adalah menyamakan metadata provider di database lokal Codex:

openai → 9router

Dengan begitu, chat lama OpenAI bisa tampil bersamaan dengan chat baru 9Router.

Tetap lakukan backup sebelum mengedit database, karena cara ini bukan fitur resmi Codex, melainkan workaround lokal.

No comments:

Post a Comment