dev-manager-tauri/.blueprint/modules/codex-model-switch.md
2026-09-21 12:53:25 +09:00

133 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: codex-model-switch
name: Codex 模型切换
status: planned
progress: 0
---
## 概述
在 DeepSeek 与官方模型之间安全切换 Codex 的模型提供方,并管理 DeepSeek API Key。
炼境侧**只读**:展示当前档位、Key 提示、冲突告警,所有写入动作转发给独立二进制 `codex-switch` 执行。
## 决策记录
- **写入职责不放炼境**:`claude-autonomy-config` 已立下规则「炼境只读,不写第三方工具配置,避免意外覆盖用户的 settings.json」。模型切换必须写 `~/.codex/config.toml`,若放进炼境核心就与该规则直接冲突。故写入交给独立 CLI,炼境只做展示与转发。
- **不引入插件系统**:炼境「工具」页(`ToolsPage.tsx`)本就是「低频独立页合并为 tabs」的容器,`v2rayn-import`、`devtools-scan` 已确立「感知宿主开发工具状态」这一模块族。新需求是该族成员,加一个 tab 即可,无需先造扩展机制。
- **Key 归 CLI 管理,落炼境 SQLite**:写配置的是 CLI,密钥就该在写入方一侧,否则 CLI 独立使用时没有密钥来源。炼境因此不新增数据表,也不接触 DeepSeek 明文密钥。
- **只物化一处**:Key 只写 `[model_providers.deepseek].experimental_bearer_token`,并停止向 `[shell_environment_policy.set]` 写 `DEEPSEEK_API_KEY`。现状两处值不一致(`sk-599a66…` vs `sk-c12beb…`)正源于此。
---
## 设计决策:Codex 模型切换工具
### 背景
- 关联现有模块:`devtools-scan`(宿主开发工具扫描)、`wsl-proxy`(第三方工具配置同步)、`claude-autonomy-config`(外部 AI CLI 配置只读展示先例)、`api-keys`(密钥保管,职责不同不复用存储)
- 现状事实:`~/.codex/config.toml` 已被 DeepSeek 官方脚本改写(`model = "deepseek-flash"`、`model_provider = "deepseek"`);`[model_providers.code-switch]` 由 cc-studio 托管且处于 `disabled_alias`;DeepSeek Key 同时存在于 provider 段与 `shell_environment_policy.set` 两处且值不同
- 用户痛点:官方脚本是一次性安装器(只有「装 flash / 装 v4-pro / 恢复到安装前」),无状态查询、无 Key 体检、无失败回滚
### 方案选择
| 方案 | 说明 | 结论 |
| --- | --- | --- |
| A 纯炼境内置模块 | Rust 直接写 `config.toml` | 否决:违反 `claude-autonomy-config` 既有规则,且炼境承担第三方配置写坏的责任 |
| B 独立 CLI + 炼境只读面板 | CLI 拥有写入与密钥;炼境只读探测 + 转发 | **选定** |
| C 完整插件系统 | 先定义 manifest / 发现 / 生命周期 | 否决:当前仅一个候选插件,属过度设计 |
### 受影响文件
新增:
- `src-tauri/src/commands/codex_switch.rs` — 只读探测 Codex 配置状态 + 定位并调用 `codex-switch` CLI
- `src/components/tools/CodexSwitchPanel.tsx` — 工具页面板
修改:
- `src-tauri/src/commands/mod.rs` — 声明 `pub mod codex_switch`
- `src-tauri/src/lib.rs` — 注册 command(静态 invoke_handler)
- `src/lib/commands.ts` — 前端封装(AGENTS.md 硬约定:新增 command 必须同步)
- `src/components/tools/ToolsPage.tsx` — 新增 `codex` tab
同仓库内(独立子项目,自带 Cargo workspace,不纳入 src-tauri 构建):
- `codex-switch/crates/core` — 纯逻辑:detect / config_edit / catalog / secret / verify / backup
- `codex-switch/crates/cli` — 命令入口:status / preview / switch / undo / set-key / doctor
### 系统边界变更
- **不新增数据表**(Key 归 CLI,炼境不落密钥)
- 新增 Tauri command:
- `get_codex_switch_status() -> CodexSwitchStatus` — 只读;含当前档位、provider、Key hint(脱敏)、CLI 是否已安装、cc-studio 冲突、残留旧条目
- `run_codex_switch_action(action, target?) -> ActionResult` — 转发给 CLI,回传 stdout/stderr 与退出码
- CLI 契约(炼境与 CLI 之间的稳定接口):
- `codex-switch status --json`
- `codex-switch preview --to <target> --json`
- `codex-switch switch --to <target>`
- `codex-switch undo`
- `codex-switch set-key --stdin`
- `codex-switch doctor --json`
### 风险点
- cc-studio 同时托管 `model_provider` / `preferred_auth_method` / `model_providers.code-switch`:写入会互相覆盖 → CLI preflight 检测托管文件,拒绝静默覆盖并告警
- CLI 未安装:面板降级为「未安装 + 获取引导」,不报错
- 配置被 CLI 改写后面板缓存陈旧 → 每次进入面板重新探测,不做前端缓存
- 二进制分发:CLI 需随炼境打包(Tauri xternalBin)或落 PATH。P0 阶段先按「PATH 查找 + 可配置路径」实现,打包留到发布前处理
- `toml_edit` 对含大量手写内容的 `config.toml` 格式保真:非拥有字段在写入前后做哈希断言,并补 fixture 回归测试
- 切换 provider 后历史会话按登录方式分组隐藏(官方文档已说明):必须由面板显式提示,否则新手误判为数据丢失
### 拆解建议
建议拆为 5 张任务卡,依赖顺序:
1. **core 骨架**(M)— `codex-switch/crates/core`:detect + config_edit(toml_edit)+ backup
2. **CLI 切换**(M)— 依赖 1:status / preview / switch / undo
3. **CLI 密钥与体检**(M)— 依赖 2:set-key(凭据管理器)+ doctor 连通性验证
4. **炼境只读探测与转发**(M)— 依赖 2 的 CLI 契约:`codex_switch.rs` + `commands.ts`
5. **炼境工具页面板**(M)— 依赖 4:`ToolsPage` 新增 tab + 面板 UI + 新手护栏文案
---
## 任务卡
> `codex-switch/` 指炼境仓库内的独立子目录,自带 Cargo workspace,不并入 `src-tauri` 的构建(对炼境现有编译零影响)。
### 📋 core:配置探测与精细改写骨架
- status: todo
- complexity: M
- files: codex-switch/crates/core/Cargo.toml, codex-switch/crates/core/src/lib.rs, codex-switch/crates/core/src/detect.rs, codex-switch/crates/core/src/config_edit.rs, codex-switch/crates/core/src/backup.rs, codex-switch/crates/core/tests/fixtures/config.toml
- acceptance: 对 fixture 执行档位切换后,非拥有字段(mcp_servers / plugins / projects / notify / shell_environment_policy)逐字节不变;config.toml 解析失败时中止且零文件修改;备份可原样还原
核心 crate 不得依赖 Tauri。`config_edit` 用 `toml_edit` 保留注释与字段顺序,写入走临时文件 + 同目录原子 rename。
### 📋 CLI:status / preview / switch / undo
- status: todo
- complexity: M
- files: codex-switch/crates/cli/Cargo.toml, codex-switch/crates/cli/src/main.rs, codex-switch/crates/cli/src/commands/status.rs, codex-switch/crates/cli/src/commands/preview.rs, codex-switch/crates/cli/src/commands/switch.rs, codex-switch/crates/cli/src/commands/undo.rs
- depends: core:配置探测与精细改写骨架
- acceptance: `status --json` 报出当前档位与 provider;`switch --to official` 与 `switch --to deepseek-flash` 可往返切换;`undo` 回到上一状态;`preview --to X --json` 输出字段级 diff 且不含明文密钥
### 📋 CLI:密钥托管与连通性体检
- status: todo
- complexity: M
- files: codex-switch/crates/core/src/secret.rs, codex-switch/crates/cli/src/commands/set_key.rs, codex-switch/crates/cli/src/commands/doctor.rs
- depends: CLI:status / preview / switch / undo
- acceptance: `set-key --stdin` 后密钥只物化到 `[model_providers.deepseek].experimental_bearer_token` 一处,且不再写入环境变量段;任何输出与日志中只出现 `sk-` + 前 6 + … + 后 4;`doctor --json` 能区分 Key 无效 / 限流 / 网络不通 / 模型名不存在
密钥存系统凭据管理器;`set-key` 接受 stdout 管道输入,不接受命令行明文参数。
### 🔵 炼境:只读探测与 CLI 转发
- status: in_progress
- complexity: M
- files: src-tauri/src/commands/codex_switch.rs, src-tauri/src/commands/mod.rs, src-tauri/src/lib.rs, src/lib/commands.ts
- depends: CLI:status / preview / switch / undo
- acceptance: `get_codex_switch_status` 返回当前档位 / provider / 脱敏 Key hint / CLI 是否已安装 / cc-studio 托管冲突 / 旧条目残留;`run_codex_switch_action` 转发 CLI 并回传 stdout、stderr 与退出码;全流程炼境不写入任何 Codex 配置文件
CLI 调用统一走 `crate::silent_cmd`(Windows 隐藏窗口)。新增 command 必须同步 `src/lib/commands.ts`。
### 📋 炼境:工具页面板与新手护栏
- status: todo
- complexity: M
- files: src/components/tools/CodexSwitchPanel.tsx, src/components/tools/ToolsPage.tsx
- depends: 炼境:只读探测与 CLI 转发
- acceptance: 「工具」页新增 Codex 模型切换 tab;CLI 未安装时显示获取引导而非报错;切换成功后提示「历史会话按登录方式分组、未删除」与「桌面端需重启」;检测到 cc-studio 托管时给出告警
面板所有写入动作经确认弹窗后转发 CLI;每次进入面板重新探测,不做前端缓存。