codex-model-change 1.0.3 → 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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/cx.py +12 -55
  3. 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 <目标>` | **批量把所有老会话切换到目标模型**(`deepseek` 或 `gpt`,需退 App);切到 gpt 时自动清理旧代理遗留的不兼容历史条目(`reasoning.content` 等),避免续聊报 `Invalid 'input[..].content'` |
54
- | `cx fix-all deepseek --limit 20` | 只批量切换最近 20 个会话 |
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 / cx fix-all gpt --limit 20
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.3"
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
- migrate-sessions 旧代理会话迁移(遗留命令,一般用 fix-all 即可)
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
- if "--limit" in args:
318
- i = args.index("--limit")
319
- limit = int(args[i + 1]) if len(args) > i + 1 else None
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
- print(f"待切换 {len(todo)} / {len(rows)} 个会话 {model} ({provider})")
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-model-change",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Switch OpenAI Codex (CLI / desktop app) between DeepSeek direct-connect and native ChatGPT. No proxy, no daemon.",
5
5
  "bin": {
6
6
  "cx": "bin/cx.js"