Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@ The non-negotiable details:
stored key file.
2. Run `node src/cli.mjs install`, then `node src/cli.mjs start`, then
`node src/cli.mjs doctor`. Doctor must report `ok` for config, catalog, router token, proxy, key,
and the app-server bridge. Install must refuse a user-owned `CODEX_CLI_PATH`; it may only install the
DSCodex wrapper when that login-session variable is absent or already DSCodex-owned. The
variable must point at the generated shim `~/.codex/dscodex/codex-cli-bridge.sh`, never
directly at `src/codex-wrapper.mjs`: GUI apps get a bare launchd PATH without Homebrew, so a
`#!/usr/bin/env node` shebang fails there and the shim embeds the absolute node path. The
bridge is macOS-only: Windows desktop apps spawn `CODEX_CLI_PATH` directly and cannot run a
script shim (CreateProcess requires an `.exe`), so on Windows `install` skips the bridge and
the `doctor` bridge check passes trivially.
and the app-server bridge state. The app-server bridge is opt-in (`node src/cli.mjs bridge enable`)
because a global `CODEX_CLI_PATH` demotes the app from its local daemon websocket (which
supports reconnect) to stdio and breaks Computer Use; `install` therefore never sets it and
actively removes DSCodex-owned copies left by older versions, including any `CODEX_CLI_PATH`
the Codex app snapshotted into `[mcp_servers.*.env]`. When enabled, `bridge enable` must refuse
a user-owned `CODEX_CLI_PATH`; the variable must point at the generated shim
`~/.codex/dscodex/codex-cli-bridge.sh`, never directly at `src/codex-wrapper.mjs`: GUI apps get
a bare launchd PATH without Homebrew, so a `#!/usr/bin/env node` shebang fails there. The shim
resolves node from PATH at runtime and only falls back to the absolute path baked at install
time. The wrapper must resolve the stock Codex binary through the shared fallback chain
(`src/real-codex.mjs`) instead of exiting when `DSCODEX_REAL_CODEX` is missing: launchctl login
variables do not survive reboots, and a stale `CODEX_CLI_PATH` must degrade to stock Codex, not
hard-fail every spawn. The bridge is macOS-only: Windows desktop apps spawn `CODEX_CLI_PATH`
directly and cannot run a script shim (CreateProcess requires an `.exe`), so on Windows the
bridge is unavailable and the `doctor` bridge check passes trivially.
3. Run `npm test`; all tests must pass.
4. The ChatGPT desktop app must be fully quit (`⌘Q`) and relaunched, and the user must start a NEW
task to see `🐳 V4 Flash`. Existing tasks keep their old model state.
Expand Down
27 changes: 18 additions & 9 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,13 @@ http://127.0.0.1:10110/<router-token>/v1 ← authenticated DSCodex loopback ro
```

`openai_base_url` points at the router and `model_catalog_json` merges V4 Flash into the catalog;
the router rewrites only that model's provider fields and forwards everything else unchanged. The
desktop app starts a transparent bridge via `CODEX_CLI_PATH`, which rewrites only model-selection
JSONL RPC before handing it to the stock bundled Codex binary; the per-provider effort/speed slots
live in that bridge (`~/.codex/dscodex/model-selections.json`).
the router rewrites only that model's provider fields and forwards everything else unchanged. Model
switching works through the catalog out of the box. The desktop app additionally offers an
**opt-in** transparent bridge (`node src/cli.mjs bridge enable`, mounted via `CODEX_CLI_PATH`),
which rewrites only model-selection JSONL RPC before handing it to the stock bundled Codex binary;
the per-provider effort/speed slots live in that bridge (`~/.codex/dscodex/model-selections.json`).
The bridge is off by default: a global `CODEX_CLI_PATH` moves the app off its local daemon
websocket (which supports reconnect) onto stdio, breaking Computer Use.

## Requirements

Expand Down Expand Up @@ -127,7 +130,7 @@ CLI note: `-m deepseek/deepseek-v4-flash` without the override may show `High`;

- Real DeepSeek tool loop and GPT OAuth passthrough verified end-to-end (`DSCODEX_TOOL_OK`,
`DSCODEX_GPT_OAUTH_OK`).
- The bridge covers default-picker changes, live-task switches, Fast restoration, and persistence
- The bridge (opt-in) covers default-picker changes, live-task switches, Fast restoration, and persistence
across app restarts; `model/list` shows `🐳 V4 Flash`, default `max`, supported `["high","max"]`,
with native GPT entries preserved.

Expand Down Expand Up @@ -180,10 +183,16 @@ CLI note: `-m deepseek/deepseek-v4-flash` without the override may show `High`;
unverified PID. Request and decompressed-body limits protect the local process from accidental
or hostile memory spikes.
- **Platform differences.** Routing, key storage, and catalog merging behave identically on every
platform; the app-server bridge (picker-state memory for the desktop app) is macOS-only. Windows
desktop apps spawn `CODEX_CLI_PATH` directly and CreateProcess cannot run a script shim (only an
`.exe`), so `install` skips the bridge on Windows and the matching `doctor` check passes
trivially. Windows uses the same `%USERPROFILE%\.codex` layout; the key file's 0600 mode is a
platform; the app-server bridge (picker-state memory for the desktop app) is macOS-only and
opt-in (`bridge enable`). It stays off by default because it demotes the app's app-server
connection from the local daemon websocket to stdio, which can break Computer Use; `bridge
disable` reverts at any time and also strips the `CODEX_CLI_PATH` copies the app snapshotted into
`[mcp_servers.*.env]`. The bridge shim resolves node from PATH at runtime and only falls back to
the absolute path baked at install time; when `DSCODEX_REAL_CODEX` is lost (launchctl login
variables do not survive reboots) the wrapper falls back to the app's bundled Codex binary
instead of exiting with an error. Windows desktop apps spawn `CODEX_CLI_PATH` directly and
CreateProcess cannot run a script shim (only an `.exe`), so the bridge is unavailable on Windows
and the matching `doctor` check passes trivially. Windows uses the same `%USERPROFILE%\.codex` layout; the key file's 0600 mode is a
no-op there and protection falls back to the account ACL. The router does not auto-start by
default; `node src/cli.mjs autostart enable` registers it at login (macOS launchd / Linux
systemd user service / Windows Task Scheduler). Crashes are relaunched automatically, while a
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ http://127.0.0.1:10110/<router-token>/v1 ← 带认证的 DSCodex 本地路由
https://chatgpt.com/backend-api/codex (OAuth 流量原样转发)
```

`openai_base_url` 指向本地路由,`model_catalog_json` 把 V4 Flash 合入模型目录;路由只改写该模型的 provider 字段,其余请求原样转发。桌面端经 `CODEX_CLI_PATH` 挂一个透明 bridge,只改写模型选择的 JSONL RPC,再交给 App 自带的原版 Codex 二进制;双槽 effort/speed 状态就存在这个 bridge 里(`~/.codex/dscodex/model-selections.json`)。
`openai_base_url` 指向本地路由,`model_catalog_json` 把 V4 Flash 合入模型目录;路由只改写该模型的 provider 字段,其余请求原样转发。模型切换只依赖目录,开箱即用。桌面端另有一个**可选**的透明 bridge(`node src/cli.mjs bridge enable` 后径 `CODEX_CLI_PATH` 挂载),只改写模型选择的 JSONL RPC,再交给 App 自带的原版 Codex 二进制;双槽 effort/speed 状态就存在这个 bridge 里(`~/.codex/dscodex/model-selections.json`)。bridge 默认不启用:全局 `CODEX_CLI_PATH` 会让 App 放弃本地 daemon websocket(支持重连)改走 stdio,破坏 Computer Use

## 环境要求

Expand Down Expand Up @@ -98,7 +98,7 @@ CLI 注意:`-m deepseek/deepseek-v4-flash` 不带覆盖参数时可能显示 `
## 已验证行为

- 真实 DeepSeek 工具循环与 GPT OAuth 旁路均端到端实测通过(`DSCODEX_TOOL_OK`、`DSCODEX_GPT_OAUTH_OK`)。
- bridge 覆盖默认 picker、已有任务切换、Fast 恢复与重启后的状态持久化;`model/list` 返回 `🐳 V4 Flash`、默认 `max`、可选 `["high","max"]`,原生 GPT 条目保留。
- bridge(可选)覆盖默认 picker、已有任务切换、Fast 恢复与重启后的状态持久化;`model/list` 返回 `🐳 V4 Flash`、默认 `max`、可选 `["high","max"]`,原生 GPT 条目保留。

## 兼容性速查

Expand All @@ -120,7 +120,7 @@ CLI 注意:`-m deepseek/deepseek-v4-flash` 不带覆盖参数时可能显示 `
- **Voice、Pets、插件、技能、MCP。** 都是客户端功能;语音由 GPT-Live 驱动,不会路由到 DeepSeek。
- **Key 存储。** 保存在 `~/.codex/dscodex/config.json`(权限 0600,目录 0700);Windows 使用当前用户 DPAPI 加密,POSIX 系统依靠仅所有者可读的文件权限。旧版 Windows 明文 key 会在下一次安装、启动或配置写入时自动迁移。介意持久化的话不要 `key set`,改用每次会话的 `DEEPSEEK_API_KEY`(或 macOS `launchctl setenv`)。运行时取值顺序:环境变量 → 存储文件 → macOS 登录会话;`key delete` 并重启路由即彻底清除。
- **本地边界。** `install` 会生成 256 位路由令牌并写入 `openai_base_url`;`start` / `serve` 会校准 CLI 自有的 URL、端口和令牌,`doctor` 会验证三者一致,不带令牌的请求返回 404。`stop` 使用同一令牌和一次性关闭令牌握手,并以实例身份原子认领 PID 状态,不会删除替代实例的状态或向未经验证的 PID 发信号;请求体和解压后请求体均有限制,避免本地进程因异常输入发生内存峰值。
- **平台差异。** 路由、key 存储、目录合并全平台一致;app-server bridge(桌面端模型菜单的状态记忆)仅 macOS——Windows 桌面端直接 spawn `CODEX_CLI_PATH`,脚本 shim 起不来(CreateProcess 只认 `.exe`),因此 Windows 上 `install` 跳过 bridge、`doctor` 对应项自动 `ok`。Windows 的配置目录同样是 `%USERPROFILE%\.codex`;key 文件的 0600 权限位在 Windows 不生效,依赖账户 ACL 保护。路由默认不随机启动;`node src/cli.mjs autostart enable` 可注册登录自启(macOS launchd / Linux systemd user service / Windows 任务计划),崩溃会被自动拉起,而手动 `stop` 是优雅退出(退出码 0),不会被复活;`autostart disable` 和 `uninstall` 都会移除自启项。未开启自启时,重启后重新 `node src/cli.mjs start` 即可(key 已持久化,无需重配)。
- **平台差异。** 路由、key 存储、目录合并全平台一致;app-server bridge(桌面端模型菜单的状态记忆)仅 macOS 且为可选(`bridge enable`)。默认不启用 bridge:它会把 App 的 app-server 连接从本地 daemon websocket 降级为 stdio,可能破坏 Computer Use;启用后 `bridge disable` 可随时回退,回退会同时清理 App 快照进 `[mcp_servers.*.env]` 的 `CODEX_CLI_PATH`。bridge shim 运行时优先用 PATH 里的 node,找不到才回退安装时焂入的绝对路径;`DSCODEX_REAL_CODEX` 丢失(重启后 launchctl 会话变量不保留)时 wrapper 会回退到 App 自带 Codex 二进制而不是报错退出。Windows 桌面端直接 spawn `CODEX_CLI_PATH`,脚本 shim 起不来(CreateProcess 只认 `.exe`),因此 Windows 上 bridge 不可用、`doctor` 对应项自动 `ok`。Windows 的配置目录同样是 `%USERPROFILE%\.codex`;key 文件的 0600 权限位在 Windows 不生效,依赖账户 ACL 保护。路由默认不随机启动;`node src/cli.mjs autostart enable` 可注册登录自启(macOS launchd / Linux systemd user service / Windows 任务计划),崩溃会被自动拉起,而手动 `stop` 是优雅退出(退出码 0),不会被复活;`autostart disable` 和 `uninstall` 都会移除自启项。未开启自启时,重启后重新 `node src/cli.mjs start` 即可(key 已持久化,无需重配)。

## 任务中思考为什么反复折叠

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dscodex",
"version": "0.5.1",
"version": "0.6.0",
"private": true,
"type": "module",
"description": "Minimal DeepSeek V4 Flash Responses API integration for Codex",
Expand Down
19 changes: 18 additions & 1 deletion src/catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,23 @@ function clone(value) {
return JSON.parse(JSON.stringify(value));
}

// Codex version bumps can turn previously optional catalog fields into
// required ones faster than the app rewrites models_cache.json, and a single
// unparseable entry breaks the whole model_catalog_json (app-server fails to
// start). Backfill known-required fields on native entries with safe
// defaults; the DeepSeek entry sets its own values explicitly.
const NATIVE_ENTRY_DEFAULTS = {
supports_reasoning_summaries: false,
};

function backfillNativeEntry(model) {
const entry = clone(model);
for (const [key, value] of Object.entries(NATIVE_ENTRY_DEFAULTS)) {
if (entry[key] === undefined) entry[key] = value;
}
return entry;
}

function replaceIdentity(value) {
if (typeof value !== "string") return value;
return value
Expand Down Expand Up @@ -74,7 +91,7 @@ export function buildCatalog(cache) {
const nativeModels = cache.models.filter((model) => model?.slug !== DEEPSEEK_PICKER_SLUG);
const template = nativeModels.find((model) => model?.slug === "gpt-5.6-sol") ?? nativeModels[0];
return {
models: [buildDeepSeekCatalogEntry(template), ...clone(nativeModels)],
models: [buildDeepSeekCatalogEntry(template), ...nativeModels.map(backfillNativeEntry)],
};
}

Expand Down
Loading