Skip to content
Merged
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
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -871,11 +871,11 @@ curl -X POST http://localhost:8080/auth/accounts/import \
### [Unreleased]

**Added**
- Dashboard 新增 Plus / Pro / PAYG 账号的 credit 余额可视化与账号池总览(Phase 1,零新增上游 traffic):`CodexUsageResponse.credits` 此前被打成 `unknown` 全量丢弃,现在 `toQuota()` 把 `has_credits / unlimited / overage_limit_reached / balance` 解析进新 `CodexQuota.credits` 槽,并把 `balance` 从 upstream 的十进制字符串转成 number(malformed payload 防御性返回 null 而不是 NaN);`AccountRegistry.updateCachedQuota` 在收到不带 credits 字段的 quota(passive header path)时保留之前已知的 credit 余额,防止每次 `/codex/responses` 响应抹掉初始 warmup 拿到的余额;新增 `usage_stats.credits_per_usd` 配置项(默认 25,即 1000 credits = $40),dashboard 端按此换算 USD 显示。前端 `AccountCard` 在 `has_credits=true` 或 `unlimited=true` 的账号上新增一行 Credit Balance(balance + USD 换算 或 "Unlimited" 标签 或 overage 红字提示),Plus 账号 has_credits=false 时整行不渲染;新增 `PoolOverview` 卡片在 `/` 主页 AccountList 上方汇总账号池:active / quota_exhausted 数量、所有携带 credits 的账号余额合计与 USD 折算、secondary used% 最高的账号 + 重置时间。新增 `formatCredits` / `creditsToUsd` / `formatUsd` 共享格式化工具,shared 类型 `AccountQuota.credits` 透传到前端。新增 `tests/unit/auth/quota-utils.test.ts` 5 个 credits 用例、`tests/unit/auth/account-pool-quota.test.ts` 2 个 updateCachedQuota credits-preserve 用例、`shared/utils/__tests__/format-credits.test.ts` 11 个格式化工具用例、`tests/unit/web/pool-overview-stats.test.ts` 7 个聚合算法用例、`tests/unit/config-schema.test.ts` 补 `credits_per_usd` 默认值断言。i18n 中英新增 8 个新键(`src/proxy/codex-types.ts`、`src/auth/types.ts`、`src/auth/quota-utils.ts`、`src/auth/account-registry.ts`、`src/config-schema.ts`、`src/proxy/codex-api.ts`、`shared/types.ts`、`shared/utils/format.ts`、`shared/i18n/translations.ts`、`web/src/App.tsx`、`web/src/components/AccountCard.tsx`、`web/src/components/PoolOverview.tsx`)
- 第三方 API key 增加 capability 分层与 OpenAI-compatible embeddings 代理:旧 key 自动按 `chat` 迁移,新增/导入/导出支持 `capabilities`,`/v1/embeddings` 只使用显式标记 `embeddings` 的 OpenAI/OpenRouter/custom key 并直连上游 `/embeddings`;Dashboard API Keys 表单新增 Chat / Embeddings 复选框和手动模型输入,避免静态 catalog 卡住 embedding/custom model。启动时无持久 key 也会创建 runtime router,后续在面板添加的 key 无需重启即可参与 chat 直连;chat 直连模型在配置 `proxy_api_key` 时补齐代理 key 校验。新增单元、前端表单和真实 OpenRouter embeddings 3 连 E2E 验证(`src/auth/api-key-pool.ts`、`src/routes/api-keys.ts`、`src/routes/embeddings.ts`、`src/proxy/upstream-router-bootstrap.ts`、`src/routes/chat.ts`、`web/src/components/ApiKeyManager.tsx`、`tests/unit/routes/embeddings.test.ts`、`web/src/components/ApiKeyManager.test.tsx`)
- Account transfer compatibility with Cockpit Tools / Sub2API / CPA: normalized quota windows now include `remaining_percent` while preserving `used_percent`; successful `GET /auth/accounts/:id/quota` calls update cached quota; account import accepts Cockpit Tools portable objects, direct `accessToken` / `refresh_token`, Sub2API exports, raw arrays, and `text/plain` token lines; account export adds `format=cockpit_tools|sub2api|cpa` alongside existing `full|minimal`; Dashboard import/export now sends JSON or token-line files without forcing `{ accounts: [...] }` and exposes the export format selector. Docs now record the `/backend-api/wham/usage` active quota path and compatibility formats (`src/auth/quota-utils.ts`, `src/proxy/rate-limit-headers.ts`, `src/routes/accounts.ts`, `src/services/account-transfer-formats.ts`, `shared/account-transfer-client.ts`, `shared/hooks/use-accounts.ts`, `web/src/components/AccountImportExport.tsx`, `docs/architecture/auth/quota.md`, `docs/architecture/auth/import-export.md`, `docs/todo/cockpit-tools-quota-import-export.md`).
- 支持 Dashboard 配置模型映射与本地自定义模型目录:`data/local.yaml` 可把客户端模型名映射到 Codex 模型、带 provider 前缀的第三方模型或已有 `model_routing` 目标;Dashboard → Settings → 模型映射可直接增删 alias 并热加载后端;`model.custom_models` 可把自定义 Codex-compatible ID 加入 `/v1/models/catalog` 并支持 `-fast` / `-high` 等后缀。ModelStore 会用本地 alias 覆盖静态 `config/models.yaml` alias,UpstreamRouter 会在内置 Claude/Gemini 自动路由前解析 alias,并在直连 provider 请求中把 outgoing `model` 改写为映射目标。新增 schema / model-store / upstream-router / route direct guard / Dashboard 组件测试覆盖配置默认值、静态 alias 覆盖、custom catalog、provider target 路由、四类直连接口(Chat / Messages / Responses / Gemini)的目标模型透传和 UI 持久化(`src/config-schema.ts`、`src/models/model-store.ts`、`src/proxy/upstream-router.ts`、`src/routes/admin/settings.ts`、`src/routes/chat.ts`、`src/routes/messages.ts`、`src/routes/responses.ts`、`src/routes/gemini.ts`、`web/src/components/ModelAliasSettings.tsx`、`tests/unit/config-schema.test.ts`、`tests/unit/models/model-store.test.ts`、`tests/unit/proxy/upstream-router.test.ts`、`tests/unit/routes/general-settings.test.ts`、`tests/unit/routes/upstream-auth-bypass.test.ts`、`web/src/components/ModelAliasSettings.test.tsx`)
- Stream-close 事件结构化落盘到 Errors tab + 审计 log:`premature stream close` / `stream-client-abort` / `stream-client-disconnect` / `stream-error` 此前只走 `console.warn` 进 `dev-YYYY-MM-DD.log`,需要 grep 才能定位,且生产模式没有 tee;新增 `src/logs/stream-close-event.ts` 把这些事件同时写到 `data/error-log.jsonl`(Errors tab 按签名分组 + 角标计数)和 `logStore`(`/admin/logs` 审计流)。覆盖 7 个调用点:`proxy-handler.ts` 两处 client abort + 一处 `UpstreamPrematureCloseError`(带 eventCount / hadReasoning / responseId / variantHash)、`response-processor.ts` 两处(`client-write-failed` 带 writtenChunks/Bytes/lastSentEvent;`upstream-error` 带 upstreamStatus)、`responses.ts` 两处 `streamPassthrough` 内部 EOF(rid / accountEntryId / variantHash 通过 `FormatAdapter.streamTranslator` 的 `streamContext` option 由 `response-processor` 透传,其它 adapter 兼容性接收并忽略)。顺手修 `error-log.ts:readAppVersion` 在 config 未加载时崩溃(unit-test 路径会撞到),改为 try/catch 兜底回退 "unknown"。新增 `tests/unit/logs/stream-close-event.test.ts` 6 个单测覆盖 4 种 kind + 缺失 rid 兜底 + numeric upstreamStatus → audit status 透传 + direct upstream provider/path;Errors tab 展开分组时会显示 sample context。下次复现 premature close 直接看 Errors tab 按 `StreamUpstreamPrematureClose` 分组拉 rid + account + closeCode,不用再 grep dev 日志(`src/logs/stream-close-event.ts`、`src/logs/error-log.ts`、`src/routes/shared/proxy-handler.ts`、`src/routes/shared/response-processor.ts`、`src/routes/responses.ts`、`tests/unit/logs/stream-close-event.test.ts`)
- Opt-in 上游请求/响应 dumper:新增 `src/utils/debug-dump.ts`,环境变量 `CODEX_PROXY_DEBUG_DUMP=1` 启用时把每次上游请求 + 流式 chunk + 终止状态 + 错误写入 `/tmp/codex-proxy-dump-<startupMs>.jsonl`(一行一事件);未启用时所有 hook 是 `if (debugDumpEnabled())` 守护下的纯 boolean check,零开销。在 `src/routes/shared/proxy-handler.ts` 加 1 个 hook(`request`,含 rid/tag/entryId/conv/implicitResumeActive/resumeReason/payload),在 `src/routes/shared/response-processor.ts` 加 3 个 hook(`upstream-chunk` 截断到 16KB、`stream-finish` 含 chunks/bytes/sawTerminal、`stream-error` 含 status/msg/body 截断到 4KB)。**privacy 警告**:dump 文件包含完整 request payload(含用户 prompt)和上游响应,路径在启动时打印一次提示 sensitive 性质。日常排查"账号轮换重试风暴" / "premature stream close" 等偶发错误时 opt-in 启用,问题复现后再 opt-out
- Pre-publish artifact smoke 拦在 stable 之前(#479):`release.yml` 把 4 个平台(mac arm64 / mac x64 / win / linux)的 Pack step 从 `--publish always` 改成 `--publish never`,新增跨平台 smoke step 用 `.github/scripts/electron-smoke.sh` 启动打包好的 binary、tail 日志拿 `Server started on port N`、curl `/health`、清进程;smoke 失败直接阻塞 `gh release upload`,artifact 不会进 GitHub Release(坏的就不发)。Linux 装 `libfuse2 + xvfb` 起虚拟显示,Windows 用 `win-unpacked/*.exe` 跳过 NSIS 安装;smoke 失败时通过 `actions/upload-artifact@v4` 把日志保留 7 天给排查。新增 `tests/unit/ci/electron-smoke-script.test.ts` 6 个单测,覆盖脚本的 fail-loud 路径(缺 RUNNER_OS / RELEASE_DIR / AppImage / 不支持的 OS),保证脚本本身坏掉时不会沉默通过。CI 时间增量约 +5 分钟(Linux 最快,Windows 需研究 GHA windows-latest 的 GUI 启动行为,首次 PR 可能要回炉)(`.github/scripts/electron-smoke.sh`、`.github/workflows/release.yml`、`tests/unit/ci/electron-smoke-script.test.ts`)
- Dashboard Errors tab + Header 浮起 badge + 渲染进程错误捕获(observability,#480 PR-2):新增 `Errors` tab(按 `name + first stack frame` 聚合,按 last_seen 降序,可展开看 sample stack;折叠后只显示一行);Header 右侧多一个红色 pulsing badge 显示未读错误数(>99 显示 `99+`),点击跳 `#/errors`;渲染进程注册 `window.addEventListener('error')` + `unhandledrejection` 在 `main.tsx` `render()` 之前,每条事件 fetch POST `/admin/error-logs/report`(不走 IPC,复用同源 dashboardAuth);`useErrorLogs` / `useErrorLogsCount` hook 30s 轮询;i18n 中英双语;`mark all read` 按钮调 `/admin/error-logs/seen` 推进 cursor;新增前端 web bundle +8KB gzipped(`web/src/error-capture.ts`、`web/src/pages/ErrorsPage.tsx`、`shared/hooks/use-error-logs.ts`、`web/src/App.tsx`、`web/src/components/Header.tsx`、`web/src/main.tsx`、`shared/i18n/translations.ts`、`tests/unit/web/error-capture.test.ts`、`shared/hooks/use-error-logs.test.ts`)
- ...([查看全部](./CHANGELOG.md))
**Changed**
- `handleProxyRequest` / `handleDirectRequest` 改为 named options object 调用契约,顺带把 private `handleNonStreaming` 的 20 个位置参数收敛成内部 options object,避免后续新增可选上下文时错位;所有 route 调用与直接 handler 测试同步迁移,并补 direct upstream route guard 锁住 adapter/raw model/format tag 传递(`src/routes/shared/proxy-handler.ts`、`src/routes/chat.ts`、`src/routes/messages.ts`、`src/routes/gemini.ts`、`src/routes/responses.ts`、`tests/unit/routes/upstream-auth-bypass.test.ts`)
Expand All @@ -885,11 +885,11 @@ curl -X POST http://localhost:8080/auth/accounts/import \
- 非流式 collect/retry 路径从 `proxy-handler.ts` 抽到独立 `non-streaming-handler.ts`,并把 `buildCodexApi`、image generation usage 标记、Codex error prefix 清理收敛到 `proxy-handler-utils.ts`,降低主 handler 对 empty-response retry / premature-close / affinity 逻辑的耦合;新增模块边界测试、collect 阶段 `previous_response_not_found` strip-retry guard、non-stream affinity metadata guard(`src/routes/shared/non-streaming-handler.ts`、`src/routes/shared/proxy-handler-utils.ts`、`src/routes/shared/proxy-handler.ts`、`tests/unit/routes/shared/non-streaming-handler-boundary.test.ts`、`tests/integration/proxy-handler.test.ts`)
- ...([查看全部](./CHANGELOG.md))
**Fixed**
- Release bump workflows now require runtime file changes in addition to meaningful commit subjects before tagging a beta or stable build. This prevents squash-promotion history divergence from re-counting old dev commits, and prevents workflow/docs/test-only fixes from producing empty Electron releases (`.github/workflows/bump-electron.yml`, `.github/workflows/bump-electron-beta.yml`, `tests/unit/ci/package-boundary.test.ts`).
- Release bump workflows now skip the release-notes workflow hotfix subject itself, so promoting the stable-notes CI fix to `master` does not create an empty desktop release on the next scheduled bump (`.github/workflows/bump-electron.yml`, `.github/workflows/bump-electron-beta.yml`, `tests/unit/ci/package-boundary.test.ts`).
- 修复 stable release notes 在手动 squash promotion 后只写 `fix: promote dev release fixes to master`、漏掉 dev 原始 PR 的问题:`release.yml` 改为调用 `.github/scripts/generate-release-notes.sh`,stable tag 若只有 promotion 内容且运行时代码树与 `origin/dev` 一致(忽略 README/package 版本文件),会回退使用 dev history 生成说明;新增单测覆盖正常 stable tag 与 squash promotion 两条路径(`.github/workflows/release.yml`、`.github/scripts/generate-release-notes.sh`、`tests/unit/ci/release-notes-script.test.ts`)
- Lockfile tarball sources now point to the official npm registry instead of `registry.npmmirror.com`, and the CI package boundary guard fails if any root/web/native lockfile resolves npm packages from a non-`registry.npmjs.org` host. Root production dependency audit is also clean after non-breaking lockfile updates for `hono`, `@hono/node-server`, `undici`, `minimatch`, and `brace-expansion`; the remaining full-audit finding is the existing Electron major-version upgrade requirement (`package-lock.json`, `web/package-lock.json`, `tests/unit/ci/package-boundary.test.ts`).
- Update checker now keeps `config/default.yaml` in sync when it auto-applies a Codex Desktop appcast version, while still writing `data/version-state.json` for cold-start runtime overrides. When a matching `data/extracted-fingerprint.json` is present, the checker also carries `chromium_version` through to version state, YAML, and in-memory config so User-Agent and `sec-ch-ua` fingerprint fields do not drift. The checked-in default fingerprint is updated to Codex Desktop `26.506.31421` / build `2620` / Chromium `146` (`src/update-checker.ts`, `config/default.yaml`, `tests/unit/update-checker.test.ts`).
- Release/promotion readiness fixes: `promote-dev-to-master.yml` now fails the workflow when `master` cannot fast-forward to `dev` instead of reporting a skipped success; `release.yml` runs stale asset cleanup under bash on every matrix runner and uses a dedicated PowerShell smoke script for Windows packaged Electron builds. Dashboard pool credit totals now use the persisted `usage_stats.credits_per_usd` value exposed by `/admin/general-settings`, and Anthropic cache-reporting tests now match the current contract where `input_tokens` excludes cached input while `cache_read_input_tokens` reports the hit count (`.github/workflows/promote-dev-to-master.yml`, `.github/workflows/release.yml`, `.github/scripts/electron-smoke.ps1`, `src/routes/admin/settings.ts`, `shared/hooks/use-general-settings.ts`, `web/src/App.tsx`, `web/src/components/PoolOverview.tsx`, `tests/unit/cache-reporting.test.ts`).
- Dashboard 的"刷新配额"按钮现在真正刷新账号 quota:`GET /auth/accounts/:id/quota` 此前只把 `/codex/usage` 结果返回给 caller、不写回 `pool.cachedQuota`。所以当 OpenAI 做 promo / window grant 把 `secondary_window.used_percent` 重置为 0% 时,proxy 仍然显示重置前的 98–100%,要等下一次真实 `/codex/responses` 请求才被动靠 `x-codex-*` 响应头追上。同一条死路也让 Pro / PAYG 账号 `credits` 块永远进不了 cachedQuota(header path 根本不带 credits)。修复:`/auth/accounts/:id/quota` 拉取上游后立刻 `pool.updateCachedQuota(id, toQuota(usage))` 写回;前端 `AccountList.onRefreshQuota` 直接调用单账号 `GET /quota`,失败时输出 warning 后刷新列表缓存(`src/routes/accounts.ts`、`web/src/components/AccountList.tsx`)
- `AccountRegistry.isAuthenticated()` 现在尊重 `quota.skip_exhausted` 配置:此前不论该开关如何配置,`isAuthenticated` 都会把 `cachedQuota` 已耗尽的活跃账号一律视作不可用,导致 `quota.skip_exhausted=false` 的部署在所有号都 `limit_reached=true` 但仍可被 `AccountLifecycle.acquire()` 取走的情况下,被 `accountPool.isAuthenticated()` 守门的路由(`/v1/chat/completions` / `/v1/messages` / `/v1/responses` / model 列表 / health)全部 401。现在 `isAuthenticated` 与 `hasAvailableAccounts` 用同一套规则:`skipExhausted ? !hasReachedCachedQuota(entry) : true`。配置默认值不变(默认仍跳过)。新增 5 个单测覆盖空池 / 默认 skip / 默认非 skip / `skip_exhausted=false` 配额耗尽路径 / disabled 账号 0 容忍(`src/auth/account-registry.ts`、`tests/unit/auth/account-pool-has-available.test.ts`)
- Claude Code 2.1.84 计费头 strip 行为新增 rotation 变体回归覆盖:实测 Claude Code 把 `x-anthropic-billing-header: cc_version=...; cc_entrypoint=cli; cch=<5hex>;` 作为 `system[0]` 独立块下发,`cc_version` 后缀和 `cch` 每请求 reroll;新增 `it.each` 覆盖 5 个真实 `cc_version` 后缀(c8e / 76b / f51 / 5b4 / 4f3)与"两次不同 cch 产出同一份 `instructions`"的不变性断言,防止后续改 strip(如改 `startsWith` → 正则、或加 inline 清洗)意外让 `cch` 漏进 `instructions` 污染上游 prompt cache(`tests/unit/translation/anthropic-to-codex.test.ts`
- Dashboard Errors tab now has a real clear action: `DELETE /admin/error-logs` removes current + backup JSONL files and the read cursor so repeated `StreamUpstreamPrematureClose` groups can be cleared from the page instead of only marked read. Anthropic setup defaults now map Opus 4.7 → `gpt-5.5` and Sonnet 4.6 → `gpt-5.4`, and the built-in Anthropic API-key catalog lists Claude Opus 4.7 (`src/logs/error-log.ts`, `src/routes/admin/error-logs.ts`, `shared/hooks/use-error-logs.ts`, `web/src/pages/ErrorsPage.tsx`, `web/src/components/AnthropicSetup.tsx`, `src/auth/api-key-catalog.ts`).
- ...([查看全部](./CHANGELOG.md))

### [v0.8.0](https://github.com/icebear0828/codex-proxy/releases/tag/v0.8.0) - 2026-02-24
Expand Down
Loading