codex-model-change 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/cx.py +12 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,8 +50,8 @@ cd codex-model-change
|
|
|
50
50
|
| `cx doctor` | 体检:key 有效性 / 直连连通性 / 会话健康 |
|
|
51
51
|
| `cx fix <会话ID>` | 修复某个打不开/报 404 的会话(ID 取 `cx status` 里显示的前 8 位即可) |
|
|
52
52
|
| `cx fix last` | 修复最近一个会话 |
|
|
53
|
-
| `cx fix-all
|
|
54
|
-
| `cx fix-all deepseek --limit
|
|
53
|
+
| `cx fix-all <目标> [--limit N]` | **批量把所有老会话切换到目标模型**(`deepseek` 或 `gpt`,需退 App);切到 gpt 时自动清理旧代理遗留的不兼容历史条目(`reasoning.content` 等),避免续聊报 `Invalid 'input[..].content'` |
|
|
54
|
+
| `cx fix-all deepseek --limit 10` | 只处理最近 10 个会话(`--limit` 可按需调整,省略则处理全部) |
|
|
55
55
|
|
|
56
56
|
**关于老会话**:每个会话记录着自己创建时的模型,`cx use` 只影响新会话——老会话继续用原模型,互不干扰。想把老会话搬到新模型:单个用 `cx fix`,全部用 `cx fix-all`(会先备份数据库和会话文件,确认后执行)。
|
|
57
57
|
|
package/cx.py
CHANGED
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
cx key <API_KEY> 保存 deepseek key 并注入 GUI 环境(launchctl setenv)
|
|
10
10
|
cx fix <会话ID> 修复单个会话(续跑一轮写入正确模型)
|
|
11
11
|
cx fix-all <目标> 把所有老会话批量切换到目标模型(需退 App)
|
|
12
|
-
例: cx fix-all deepseek
|
|
13
|
-
cx migrate-sessions 把旧代理时代的 deepseek 会话迁移为直连 id(需退 App)
|
|
12
|
+
例: cx fix-all deepseek --limit 10 (只改最近 10 个)
|
|
14
13
|
"""
|
|
15
14
|
import json, os, re, glob, sqlite3, subprocess, sys, shutil, time, urllib.request, urllib.error
|
|
16
15
|
|
|
17
|
-
CX_VERSION = "1.0.
|
|
16
|
+
CX_VERSION = "1.0.4"
|
|
18
17
|
|
|
19
18
|
CODEX_HOME = os.environ.get("CODEX_HOME", os.path.expanduser("~/.codex"))
|
|
20
19
|
CONFIG = os.path.join(CODEX_HOME, "config.toml")
|
|
@@ -35,7 +34,6 @@ wire_api = "responses"
|
|
|
35
34
|
"""
|
|
36
35
|
|
|
37
36
|
GPT_MODEL = "gpt-5.6-sol"
|
|
38
|
-
DS_IDS = ("deepseek/deepseek-v4-flash", "deepseek-v4-flash")
|
|
39
37
|
|
|
40
38
|
HELP = """cx — Codex 模型直连切换器
|
|
41
39
|
|
|
@@ -49,7 +47,7 @@ HELP = """cx — Codex 模型直连切换器
|
|
|
49
47
|
fix <会话ID|last> 修复单个会话(ID 取 status 里显示的前 8 位即可)
|
|
50
48
|
fix-all <目标> 批量把所有老会话切换到目标模型(需退 App)
|
|
51
49
|
用法: cx fix-all deepseek|gpt [--limit N]
|
|
52
|
-
|
|
50
|
+
例: cx fix-all deepseek --limit 10 (只改最近 10 个)
|
|
53
51
|
version 显示 cx 版本
|
|
54
52
|
help 显示本帮助
|
|
55
53
|
|
|
@@ -263,9 +261,6 @@ def cmd_fix():
|
|
|
263
261
|
print(tail)
|
|
264
262
|
ok("修复完成") if r.returncode == 0 else err(f"修复失败 exit={r.returncode}")
|
|
265
263
|
|
|
266
|
-
def is_ds(model):
|
|
267
|
-
return isinstance(model, str) and (model in DS_IDS or model.startswith("deepseek"))
|
|
268
|
-
|
|
269
264
|
# ---------- 批量切换老会话 ----------
|
|
270
265
|
def sanitize_rollout(path):
|
|
271
266
|
"""移除旧代理时代 OpenAI 不兼容的 reasoning 条目(id 为 rs_ocx_*,或带 content 字段)。
|
|
@@ -312,11 +307,14 @@ def rewrite_rollout(path, model, provider):
|
|
|
312
307
|
|
|
313
308
|
def cmd_fix_all():
|
|
314
309
|
args = sys.argv[2:]
|
|
315
|
-
which = args[0] if args else ""
|
|
310
|
+
which = args[0] if args and not args[0].startswith("-") else ""
|
|
316
311
|
limit = None
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
312
|
+
for i, a in enumerate(args):
|
|
313
|
+
if a == "--limit" and i + 1 < len(args):
|
|
314
|
+
limit = int(args[i + 1]) if args[i + 1].isdigit() else None
|
|
315
|
+
elif a.startswith("--limit="):
|
|
316
|
+
v = a.split("=", 1)[1]
|
|
317
|
+
limit = int(v) if v.isdigit() else None
|
|
320
318
|
if which not in ("deepseek", "gpt"):
|
|
321
319
|
err("用法: cx fix-all deepseek|gpt [--limit N] (--limit N 只改最近 N 个,默认全部)")
|
|
322
320
|
model, provider = (DS_MODEL, "deepseek") if which == "deepseek" else (GPT_MODEL, "openai")
|
|
@@ -337,7 +335,8 @@ def cmd_fix_all():
|
|
|
337
335
|
todo.append((tid, m, p, rp, needs_model))
|
|
338
336
|
if not todo:
|
|
339
337
|
ok(f"所有会话已经是 {model},无需切换"); db.close(); return
|
|
340
|
-
|
|
338
|
+
scope = f"最近 {limit} 个中" if limit else ""
|
|
339
|
+
print(f"{scope}待切换 {len(todo)} / {len(rows)} 个会话 → {model} ({provider})")
|
|
341
340
|
if input("确认执行? [y/N] ").strip().lower() != "y":
|
|
342
341
|
info("已取消"); db.close(); return
|
|
343
342
|
ts = time.strftime("%Y%m%d-%H%M%S")
|
|
@@ -357,47 +356,6 @@ def cmd_fix_all():
|
|
|
357
356
|
db.commit(); db.close()
|
|
358
357
|
ok(f"已切换 {n} 个会话 → {model}。重开 App 生效")
|
|
359
358
|
|
|
360
|
-
def cmd_migrate():
|
|
361
|
-
force = "--yes" in sys.argv
|
|
362
|
-
if app_running() and not force:
|
|
363
|
-
err("请先完全退出 ChatGPT/Codex App(或加 --yes 跳过检查,不推荐)")
|
|
364
|
-
db = sqlite3.connect(os.path.join(CODEX_HOME, "state_5.sqlite"))
|
|
365
|
-
rows = db.execute("SELECT id, model, rollout_path FROM threads WHERE model LIKE 'deepseek%'").fetchall()
|
|
366
|
-
if not rows: ok("没有需要迁移的会话"); return
|
|
367
|
-
ts = time.strftime("%Y%m%d-%H%M%S")
|
|
368
|
-
bak = os.path.join(CODEX_HOME, f"state_5.sqlite.bak.cx-migrate-{ts}")
|
|
369
|
-
shutil.copy2(os.path.join(CODEX_HOME, "state_5.sqlite"), bak)
|
|
370
|
-
ok(f"数据库已备份: {bak}")
|
|
371
|
-
n = 0
|
|
372
|
-
for tid, model, path in rows:
|
|
373
|
-
if os.path.exists(path):
|
|
374
|
-
bakj = path + f".bak.cx-{ts}"
|
|
375
|
-
if not os.path.exists(bakj): shutil.copy2(path, bakj)
|
|
376
|
-
new_lines = []
|
|
377
|
-
changed = False
|
|
378
|
-
for line in open(path, encoding="utf-8"):
|
|
379
|
-
if '"turn_context"' in line or '"session_meta"' in line:
|
|
380
|
-
try:
|
|
381
|
-
d = json.loads(line)
|
|
382
|
-
pl = d.get("payload", {})
|
|
383
|
-
if d.get("type") == "session_meta":
|
|
384
|
-
if is_ds(pl.get("model")): pl["model"] = DS_MODEL; changed = True
|
|
385
|
-
if pl.get("model_provider") == "openai": pl["model_provider"] = "deepseek"; changed = True
|
|
386
|
-
elif d.get("type") == "turn_context":
|
|
387
|
-
if is_ds(pl.get("model")): pl["model"] = DS_MODEL; changed = True
|
|
388
|
-
cm = pl.get("collaboration_mode") or {}
|
|
389
|
-
st = cm.get("settings") or {}
|
|
390
|
-
if is_ds(st.get("model")): st["model"] = DS_MODEL; changed = True
|
|
391
|
-
if changed: line = json.dumps(d, ensure_ascii=False) + "\n"
|
|
392
|
-
except Exception: pass
|
|
393
|
-
new_lines.append(line)
|
|
394
|
-
if changed:
|
|
395
|
-
open(path, "w", encoding="utf-8").writelines(new_lines); n += 1
|
|
396
|
-
db.execute("UPDATE threads SET model=?, model_provider='deepseek' WHERE model LIKE 'deepseek%'", (DS_MODEL,))
|
|
397
|
-
db.commit()
|
|
398
|
-
ok(f"已迁移 {n} 个 rollout / {len(rows)} 条记录 → deepseek-chat")
|
|
399
|
-
db.close()
|
|
400
|
-
|
|
401
359
|
def main():
|
|
402
360
|
if len(sys.argv) < 2: cmd_status(); return
|
|
403
361
|
c = sys.argv[1]
|
|
@@ -407,7 +365,6 @@ def main():
|
|
|
407
365
|
elif c == "doctor": cmd_doctor()
|
|
408
366
|
elif c == "fix": cmd_fix()
|
|
409
367
|
elif c == "fix-all": cmd_fix_all()
|
|
410
|
-
elif c == "migrate-sessions": cmd_migrate()
|
|
411
368
|
elif c in ("version", "-V", "--version"): print(f"cx {CX_VERSION}")
|
|
412
369
|
elif c in ("-h", "--help", "help"): print(HELP)
|
|
413
370
|
else: err(f"未知命令: {c}(cx help 查看用法)")
|
package/package.json
CHANGED