Skip to content

feat: billing correction, credit quota, scheduler modes, image-to-image, 5h/7d cost, and codex-auto-review#144

Merged
james-6-23 merged 37 commits into
james-6-23:mainfrom
DeliciousBuding:iteration/may-2026-v2
May 20, 2026
Merged

feat: billing correction, credit quota, scheduler modes, image-to-image, 5h/7d cost, and codex-auto-review#144
james-6-23 merged 37 commits into
james-6-23:mainfrom
DeliciousBuding:iteration/may-2026-v2

Conversation

@DeliciousBuding
Copy link
Copy Markdown
Contributor

@DeliciousBuding DeliciousBuding commented May 20, 2026

概要

基于 fix/oauth-proxy-url-fallback,合计 37 commits,30 个文件,+1780/-151。

覆盖 11 个 issue/PR:定价修正、credit 额度策略、调度模式、图生图、5h/7d 成本展示、codex-auto-review 模型注册、生图稳定性、Docker 安全、OAuth 代理回退等。10+ 轮多模型交叉审查,全量测试通过。

Closes #129, Closes #132, Closes #133, Closes #134, Closes #135, Closes #136, Closes #138, Closes #139, Closes #140, Closes #141, Closes #143


定价

gpt-5.5 修正

之前跟 gpt-5.4 同价($2.50/$15.00 per 1M tokens),实际 OpenAI 官方定价是 2 倍:

Tier Input Cached Output
Standard $5.00 $0.50 $30.00
Priority $12.50 $1.25 $75.00
Flex $2.50 $0.25 $15.00

gpt-5.5-pro、gpt-5.4-pro 新增独立定价($30/$180),通过 normalizeCodexBillingModel 前缀匹配优先于通用规则。

长上下文溢价

输入 token 超过 272,000 时自动切换到长上下文定价(input 2x, output 1.5x),覆盖 gpt-5.5、gpt-5.5-pro、gpt-5.4、gpt-5.4-pro。ModelPricing 新增 Long* 系列字段,CalculateCostBreakdown 自动判断。

codex-auto-review

Codex 内部自动审批模型,规格 272K context + 四档 thinking。实测上游返回 "model": "gpt-5.4",定价映射到 gpt-5.4 档。大小写均匹配。已注册到 builtin model list。


credit 额度策略 (#141)

accounts 表新增两列:

  • credit_enabled — 标记账号存在额外 credit
  • credit_skip_usage_window — 跳过 5h/7d 用量窗口限制

后者需要前者为 true 才生效。管理后台通过 PATCH /api/admin/accounts/:id/credit 支持部分更新。含 DisallowUnknownFields 校验和 rows-affected 检查。


调度模式 (#133)

system_settings 新增 scheduler_mode

  • round_robin(默认)——按 dispatch score 降序,cursor 轮转
  • remaining_quota——按 7d usage 升序,优先低用量账号

切换时实时重排全部 tier bucket。Settings 页面提供下拉选择器。


5h/7d 窗口成本 (#143)

账号列表新增成本列,按 5h 和 7d 额度窗口分别统计美元消耗。GetAccountBilledSincereset_5h_at / reset_7d_at 聚合 usage_logs.account_billed


图生图 (#135, #136)

Image Studio 新增 image-to-image 模式。支持上传参考图、输入 prompt 生成。后端通过 POST /api/admin/images/edit-jobs

  • 前端:上传前校验输入图片数量、.slice(0, MAX_INPUT_IMAGES) 防竞态、显式 forceMode 参数防路由错误
  • 后端:job 创建前同步校验 MaxImageEditInputCount
  • 生图稳定性:流式错误不再在同一连接换号重试、消除双重写入、补齐错误路径 usage log、PNG→JPEG 自动回退

OAuth 代理回退 (#138)

当 OAuth session 和请求均未指定 proxy_url 时,回退到系统默认代理(h.store.GetProxyURL())。修复地理受限地区 exchange-code 直接调用 auth.openai.com 导致 403 → 502 的问题。


Docker (#134)

SQLite compose 端口绑定改为 127.0.0.1(通过 BIND_HOST 环境变量可覆盖)。CONFIGURATION.md 区分 BIND_HOST(Docker 端口发布地址)与 CODEX_BIND(进程监听地址)。


Bug 修复

  • modelMapping 冷启动丢失:scheduler_mode 改动误删了 s.modelMapping.Store() 调用,重启后 prompt filter 不加载。已恢复。
  • credit 字段 Scan 错位:credit_enabled/credit_skip_usage_window/score_bias_override 三个字段的 SELECT 列序与 Scan 参数顺序不一致,运行时数据损坏。已修正。
  • X-Codex-Beta-Features 透传:新增到 codexAllowedForwardHeaders
  • 图生图 rerun 路由:submitJob 不再依赖 stale UI state,显式 forceMode 参数。
  • credit 开关 a11y:新增 aria-label、focus-visible 样式、disabled 状态 cursor 变化。
  • remaining_quota README 中文歧义修正。
  • API 文档重复模型 ID 修正。

兼容性

  • 全部 DDL 使用 IF NOT EXISTS + 安全默认值
  • 无删除字段或接口
  • 无 breaking change
  • 升级:git pull && docker compose restart

DeliciousBuding and others added 30 commits May 17, 2026 16:34
…_url

When adding accounts via OAuth flow, if no proxy_url is provided in
either the generate-auth-url step or the exchange-code step, the
request to auth.openai.com goes direct — which fails from
geo-blocked regions (HK, etc.) with 403 or Cloudflare 502.

Add a fallback to `h.store.GetProxyURL()` (the system default proxy)
in both `ExchangeOAuthCode` and `OAuthCallback` handlers, so OAuth
token exchange always goes through a working proxy chain.

Fixes the issue where the admin UI's "Add Account via OAuth" button
returns a Cloudflare 502 HTML page instead of a proper error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Credit quota (james-6-23#141):
- Add credit_enabled / credit_skip_usage_window to accounts
- DB migrations, Account struct, admin API endpoint
- AccountUsageModal toggle switches with i18n
- Credit accounts skip usage window checks in scheduler

Scheduler mode (james-6-23#133):
- scheduler_mode: round_robin (default) / remaining_quota
- remaining_quota sorts by usage percentage ascending
- FastScheduler, Store, Settings API, frontend dropdown
- All test calls updated

Image studio (james-6-23#135, james-6-23#136):
- admin/image_studio.go: add image-to-image generation mode
- proxy/images.go: improve error handling and retry logic
- proxy/admin_images.go: add image-to-image endpoint
- Frontend: add image-to-image tab in ImageStudio

Docker security (james-6-23#134):
- SQLite compose files bind to 127.0.0.1 by default

Co-Authored-By: Claude
- Add mode toggle: text-to-image / image-to-image
- File upload with drag-and-drop, base64 conversion
- Thumbnail preview grid with remove buttons
- Wire createImageEditJob API call
- Add 'upload' i18n key (zh/en)

Co-Authored-By: Claude
- Register missing PATCH /accounts/:id/credit route
- Add updateAccountCredit function to frontend api.ts
- Add credit_enabled/credit_skip_usage_window to accountResponse
  and populate from database row in ListAccounts

Co-Authored-By: Claude
Previous change removed ${BIND_HOST:-0.0.0.0} in favor of hardcoded
127.0.0.1. This breaks users who set BIND_HOST=0.0.0.0 in .env.
Now defaults to 127.0.0.1 (secure) but allows override via env var.

Co-Authored-By: Claude
CreditEnabled and CreditSkipUsageWindow were inserted into the SELECT
before ScoreBiasOverride, but the Scan() targets were in the wrong
order (ScoreBiasOverride first). This caused all three values to be
silently corrupted at runtime.

In GetAccountByID, there was also a duplicate &a.CreditEnabled and
missing &a.ScoreBiasOverride - same root cause from the credit agent.

Co-Authored-By: Claude
Image/Proxy fixes (11 bugs):
- CRITICAL: fix double-write in non-stream retry path
- HIGH: add client disconnect check to prevent account drain
- Add 20MB upload limit, 100MB base64 limit, 10-image cap
- Add PNG→JPEG fallback for image edit jobs
- Fix saveImageJobAssets timeout (use ctx, not Background)
- Fix rerunFromJob dropping input_images on edit jobs
- Use Promise.allSettled, optimize base64 decode
- Set HTTP client Timeout to 10min safety net

Auth/Scheduler fixes (7 bugs):
- SetSchedulerMode now re-sorts all buckets
- Rebuild respects schedulerMode for initial sort
- GetSchedulerMode fallback for nil settings
- usageExhaustedLocked respects CreditSkipUsageWindow
- Reset zeroCursor on each scan pass
- Add remaining_quota test coverage (3 new tests)
- Wire SetBaseLimit into Store.SetMaxConcurrency

Docs: README, zh-CN README, CONFIGURATION, API docs updated

Co-Authored-By: Claude
- Fix broken api.ts where updateAccountCredit was injected into
  getAccountUsage body (sed corruption), breaking frontend build
- Update billing tests for corrected gpt-5.5 pricing ($5/$30)
- Rewrite TestGPT55PricingMatchesGPT54Fallback to verify 2x pricing

Co-Authored-By: Claude
When streaming image generation and a mid-stream error is retryable,
do not retry if SSE data has already been committed to the client.
Retrying would interleave SSE events from two different upstream
accounts on the same connection.

Co-Authored-By: Claude
Read errors (content policy, safety refusals, upstream failures)
were not logged to usage_logs or reported to account health metrics.
Add logUsageForRequest calls before non-retryable returns in both
non-stream and stream error paths.

Co-Authored-By: Claude
Add "Cost (USD)" column showing account_billed from usage stats.
Column is toggleable via column settings. Data already returned by
backend (account_billed field in accountResponse).

Co-Authored-By: Claude
Replace single total cost column with windowed billing display:
- Add GetAccountBilledSince() to sum account_billed per time window
- Add billed_5h / billed_7d to accountResponse
- Display "5h: $X.XX / 7d: $Y.YY" in accounts table
- Windows based on reset_5h_at / reset_7d_at from account state

Co-Authored-By: Claude
…ode change

Commit fd7b5a2 accidentally deleted s.modelMapping.Store() and gated
SetPromptFilterConfig behind the ModelMapping != "" condition. This
caused model mapping to never load from database on cold start, and
prompt filtering to not initialize unless model mapping was also set.

Co-Authored-By: Claude
… guard

streamStarted was set before streamImagesResponse was called, making
the retry guard always dead (streamStarted always true). Replace with
c.Writer.Written() which checks if headers have actually been committed,
allowing retry when no data has been sent to the client yet.

Co-Authored-By: Claude
…editEnabled gating

- PATCH /accounts/:id/credit now uses tri-state (*bool): nil fields
  are left unchanged, preventing silent zeroing of unprovided fields
- SetSchedulerMode validates mode string, coerces unknowns to round_robin
- CreditSkipUsageWindow now gated behind CreditEnabled (both must be true)

Co-Authored-By: Claude
- Verify empty mode coerces to round_robin
- Verify SetSchedulerMode re-sorts buckets for remaining_quota
- Verify lowest-usage account picked after mode switch

Co-Authored-By: Claude
…o data sent

When streamImagesResponse returns an error before any SSE data was
committed (e.g., streaming not supported), the client received a
dropped connection with no error. Now writes a JSON error response
when c.Writer.Written() is false on the non-retryable fallthrough.

Co-Authored-By: Claude
- README: enriched feature table, OAuth PKCE section, credit docs
- CONFIGURATION: added BIND_HOST, CODEX_PROXY_URL, scheduler_mode
- API: added credit/billed fields, image edit-jobs endpoint, settings
- New CHANGELOG.md summarizing all 17 iteration commits

Co-Authored-By: Claude
…ilies

- gpt-4o: add CacheReadPricePerMToken $1.25 (was missing)
- gpt-4o-mini: add CacheReadPricePerMToken $0.075
- New families: gpt-4.1, gpt-4.1-mini, gpt-4.1-nano
- New families: o3, o4-mini, o3-mini
All prices verified against official OpenAI pricing (May 2026).

Co-Authored-By: Claude
Models with long context pricing (gpt-5.5, gpt-5.5-pro, gpt-5.4,
gpt-5.4-pro) now automatically use elevated rates when input_tokens
exceeds 272,000. Pricing verified against official OpenAI docs.

Also add gpt-4o cache read pricing ($1.25/M) and gpt-4.1 / o-series
families with correct cache prices.

Co-Authored-By: Claude
gpt-5.5-pro and gpt-5.4-pro were swallowed by generic gpt-5.5/gpt-5.4
cases, making their $30/$180 pricing rules dead code. Add explicit
-pro cases before generic matches so pro variants route correctly.

Add TestProModelsHaveCorrectPricing to prevent regression.

Co-Authored-By: Claude
codex-auto-review is Codex's automatic approval review model:
- 272K context, 128K max output, thinking low/medium/high/xhigh
- Tools support for approval evaluation
- Mapped to gpt-5.3-codex pricing tier ($1.75/$14/M)

Source: CLIProxyAPI models.json registry.

Co-Authored-By: Claude
…match)

codex-auto-review has 272K context + 4 thinking levels, matching
gpt-5.4 specs exactly. Previously mapped to gpt-5.3-codex (.75/$14)
which has only 128K context and no thinking support.
Findings from CPA analysis and live testing:
- Model only works via chatgpt.com/backend-api/codex, not public API
- Official codex_client_models.json: visibility=hide, shell_type=shell_command
- Available on Plus/Pro/Team/Business plans, explicitly excludes free
- codex2api currently only proxies public API, so this model is disabled
- Pricing remains mapped to gpt-5.4 ($2.50/$15.00) based on spec match

Co-Authored-By: Claude
Tested on hk2 backend API (chatgpt.com/backend-api/codex):
upstream response returns "model":"gpt-5.4" for codex-auto-review
requests. Model is confirmed as a gpt-5.4 routing alias.

Pricing at gpt-5.4 tier verified correct.

Co-Authored-By: Claude
- TestLongContextPricingTriggersAbove272KTokens
- TestLongContextPricingWithPriorityTier
- TestLongContextPricingDoesNotApplyWhenNoLongPricingDefined
- TestProModelsHaveCorrectPricing (existing, verified)

Co-Authored-By: Claude
Previously zeroCursor.Store(0) was at the outer loop, so after scanning
the Healthy tier (failing), the cursor advanced and Warm/Risky tiers
started from a stale position — violating lowest-usage-first ordering.

Now resets per-tier so each bucket independently scans from index 0.

Co-Authored-By: Claude
Allows downstream clients to opt into Codex beta features
(e.g. responses_websockets) by passing the header through
to the upstream backend API.

Co-Authored-By: Claude
Copilot AI review requested due to automatic review settings May 20, 2026 10:46
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

Adds scheduler modes and per-account credit controls, expands billing pricing and exposes cost APIs, implements image-edit (image-to-image) jobs and hardened proxy/image handling, persists scheduler_mode and credit columns, updates admin endpoints and frontend UIs, and documents Docker localhost-binding and related config.

Changes

Scheduler Mode Configuration & Account Credit Controls

Layer / File(s) Summary
Scheduler mode & FastScheduler
auth/fast_scheduler.go
FastScheduler gains a schedulerMode field and methods; mode-dependent bucket sorting and acquisition cursor behavior implemented (round_robin vs remaining_quota).
Store, account credit fields, and DB wiring
auth/store.go, database/postgres.go, database/sqlite.go
Adds Account credit flags, Store schedulerMode field and Get/Set methods, UpdateAccountCredit API, and DB migrations/columns for credit fields and scheduler_mode; GetAccountBilledSince added for windowed billing queries.
Scheduler tests
auth/fast_scheduler_test.go
Tests updated to pass explicit scheduler mode; new tests validate remaining_quota ordering, tie-breakers, and runtime mode switching.
Admin handler and settings
admin/handler.go
Adds PATCH /api/admin/accounts/:id/credit and POST /api/admin/images/edit-jobs routes; accountResponse includes credit and billed fields; settingsResponse/update apply scheduler_mode.

Billing Cost Calculation & Tests

Layer / File(s) Summary
Exported billing APIs and long-context pricing
database/billing.go
ModelPricing extended with long-context and priority fields; GetModelPricing exported; CalculateCost/CalculateCostBreakdown added to compute costs using long-context thresholds and priority overrides.
Billing tests
database/billing_test.go
Tests updated to use GetModelPricing and add coverage for GPT-5.5/5.4 differences, pro models, long-context behavior, and codex-auto-review normalization.

Image Edit Job Creation & Resilience

Layer / File(s) Summary
Admin image edit job and execution
admin/image_studio.go, proxy/admin_images.go
CreateImageEditJob validates inputs, stores template_id and input_images, starts runImageEditJob which uses job-scoped ctx, calls GenerateImageEditForAdmin, handles PNG→JPEG fallback, saves assets, and updates job state.
Proxy image handling and retries
proxy/images.go, proxy/executor.go
Adds base64/file size limits, multipart upload size and empty-file checks, input-count validation, bounded maxImageAttempts with context cancellation checks, improved error handling to avoid double-writes, shouldRetryImageStreamError gating, and pooled client timeout plus header allowlist updates.

Frontend, API client, and Localization

Layer / File(s) Summary
Frontend API and types
frontend/src/api.ts, frontend/src/types.ts
Adds updateAccountCredit and createImageEditJob API methods; AccountRow gains billed and credit fields; SystemSettings adds scheduler_mode; CreateImageJobPayload supports input_images.
Frontend UI and i18n
frontend/src/components/AccountUsageModal.tsx, frontend/src/pages/Accounts.tsx, frontend/src/pages/Settings.tsx, frontend/src/pages/ImageStudio.tsx, frontend/src/locales/*
AccountUsageModal adds credit toggles and async update; accounts table adds billed column; settings page adds scheduler_mode Select; ImageStudio adds image-to-image mode, file upload/preview, payload input_images, and rerun support; en/zh locales updated.

Documentation & Configuration

Layer / File(s) Summary
Docker & env notes, README, API docs, CONFIGURATION
.env.example, .env.sqlite.example, docker-compose.sqlite*.yml, README.md, README.zh-CN.md, docs/API.md, docs/CONFIGURATION.md, docs/CHANGELOG.md
Documents BIND_HOST localhost default and override, documents scheduler modes/credit flags/windowed billing/Image Studio and OAuth PKCE admin flows, updates API examples and settings docs, and records May 2026 changelog.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 Schedulers hop in round or by remaining quota, bright,
Credits skip windows so some accounts keep flight,
Images edit from pictures, retries bounded tight,
Docs bind ports to localhost and make the defaults right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main features added in the changeset: billing correction, credit quota, scheduler modes, image-to-image support, 5h/7d cost tracking, and codex-auto-review.
Linked Issues check ✅ Passed All linked issue objectives are met: #132 gpt-5.5 pricing corrected with distinct tiers; #133 scheduler mode for remaining-quota added; #134 SQLite BIND_HOST documentation and defaults fixed; #135 image-to-image upload support implemented; #136 streaming/retry improvements and PNG→JPEG fallback added; #140 499 error handling via improved stream logic; #141 credit flags (credit_enabled, credit_skip_usage_window) implemented; #143 5h/7d window cost tracking added.
Out of Scope Changes check ✅ Passed All changes are in scope. Frontend updates (AccountUsageModal, ImageStudio, Settings, locales) support the new features. Database/store changes implement credit and scheduler modes. Handler updates expose new admin endpoints. Proxy updates handle image-to-image and improve error handling. No unrelated changes detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/API.md (1)

290-292: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

修正模型列表示例中的重复 ID。

Line 291 与 Line 290 都是 gpt-5.5,示例列表出现重复条目,容易误导调用方。

🧩 建议修正
-    { "id": "gpt-5.5", "object": "model", "owned_by": "openai" },
-    { "id": "gpt-5.5", "object": "model", "owned_by": "openai" },
+    { "id": "gpt-5.5", "object": "model", "owned_by": "openai" },
+    { "id": "gpt-5.4", "object": "model", "owned_by": "openai" },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/API.md` around lines 290 - 292, 示例模型列表中出现重复的 "gpt-5.5" 条目,导致误导调用方;请在
docs/API.md 中找到模型数组里重复的 { "id": "gpt-5.5", "object": "model", "owned_by":
"openai" }(第二个出现的条目),将其删除或替换为正确且唯一的模型 ID(例如改为 "gpt-5.4" 或其他真实模型 ID),确保最终模型列表中的
id 值唯一且与实际可用模型一致。
🧹 Nitpick comments (6)
docs/API.md (1)

484-488: ⚡ Quick win

补充 credit 字段的生效前提,避免误配置。

建议在参数说明中明确:credit_skip_usage_window 仅在 credit_enabled=true 时生效。

📝 建议补充
-| credit_skip_usage_window   | bool  | 否   | 跳过 7 天/5 小时用量窗口惩罚,省略时保持原值 |
+| credit_skip_usage_window   | bool  | 否   | 跳过 7 天/5 小时用量窗口惩罚(仅在 `credit_enabled=true` 时生效),省略时保持原值 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/API.md` around lines 484 - 488, Update the parameter table so the
description for credit_skip_usage_window clearly states it only applies when
credit_enabled is true; modify the row for credit_skip_usage_window to add a
parenthetical or sentence like "仅在 credit_enabled=true 时生效" (or the English
equivalent) and, if helpful, add a short note to the credit_enabled row
indicating that related credit fields (e.g., credit_skip_usage_window) depend on
it.
docs/CONFIGURATION.md (1)

46-46: ⚡ Quick win

区分 BIND_HOST 与应用监听地址,减少运维误解。

Line 46 建议将 BIND_HOST 描述为“Docker 端口发布绑定地址”,避免与 CODEX_BIND(应用监听地址)混淆。

🔧 建议修正
-| `BIND_HOST` | 否 | `127.0.0.1`(SQLite)/ `0.0.0.0`(PostgreSQL) | HTTP 绑定地址。SQLite compose 默认 `127.0.0.1` 仅本机访问;标准 compose 默认 `0.0.0.0` 所有网络接口 |
+| `BIND_HOST` | 否 | `127.0.0.1`(SQLite)/ `0.0.0.0`(PostgreSQL) | Docker 端口发布绑定地址(非进程监听地址)。SQLite compose 默认 `127.0.0.1` 仅本机访问;标准 compose 默认 `0.0.0.0` 所有网络接口 |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/CONFIGURATION.md` at line 46, Update the BIND_HOST environment variable
description to clarify it is the Docker/container port publish bind address
(used by Docker/compose to bind host network interfaces) and not the application
listen address; explicitly mention CODEX_BIND as the application listen address
(socket/interface the app binds to inside the container) so readers won’t
confuse the two (edit the table row referencing BIND_HOST and add a short note
referencing CODEX_BIND for contrast).
admin/image_studio.go (1)

337-340: ⚡ Quick win

Consider validating input image count before creating the job.

The handler validates that input_images is non-empty but does not check against MaxImageEditInputCount. The count validation happens later in buildAdminImageEditRequest, causing the job to be created and then fail asynchronously. For consistent UX, consider adding early validation:

 if len(req.InputImages) == 0 {
     writeError(c, http.StatusBadRequest, "图生图需要上传参考图片")
     return
 }
+if len(req.InputImages) > proxy.MaxImageEditInputCount {
+    writeError(c, http.StatusBadRequest, fmt.Sprintf("参考图片数量超过限制 (%d, 最多 %d)", len(req.InputImages), proxy.MaxImageEditInputCount))
+    return
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/image_studio.go` around lines 337 - 340, The handler currently only
checks req.InputImages non-empty but defers max-count validation to
buildAdminImageEditRequest, causing jobs to be created then fail asynchronously;
update the handler (the function handling the request in admin/image_studio.go)
to also validate len(req.InputImages) <= MaxImageEditInputCount before creating
the job and call writeError(c, http.StatusBadRequest, ...) when exceeded,
mirroring the validation performed in buildAdminImageEditRequest so failures are
returned synchronously; reference req.InputImages, MaxImageEditInputCount,
buildAdminImageEditRequest, and writeError when locating where to add the early
check.
database/postgres.go (1)

3367-3395: ⚡ Quick win

Missing rows-affected check may silently ignore updates to non-existent accounts.

UpdateAccountCredit doesn't check if any rows were affected, unlike similar functions like SetAccountEnabled (line 3346) which return sql.ErrNoRows when no rows match. This could make it harder to detect when an update targets a non-existent or deleted account.

Suggested fix
-	_, err := db.conn.ExecContext(ctx, query, args...)
-	return err
+	res, err := db.conn.ExecContext(ctx, query, args...)
+	if err != nil {
+		return err
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return err
+	}
+	if affected == 0 {
+		return sql.ErrNoRows
+	}
+	return nil
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/postgres.go` around lines 3367 - 3395, UpdateAccountCredit currently
executes the UPDATE without checking whether any row was modified, so updates
targeting non-existent accounts are silently ignored; modify UpdateAccountCredit
to inspect the sql.Result from db.conn.ExecContext (via RowsAffected) and if
RowsAffected == 0 return sql.ErrNoRows (matching behavior in SetAccountEnabled),
otherwise return nil or the Exec error—use the existing variables (query, args)
and handle the result before returning.
database/billing.go (1)

358-375: 💤 Low value

Redundant struct copy in calculateCostBreakdown wrapper.

The wrapper function copies all fields from the returned CostBreakdown into a new struct with identical values. Since CostBreakdown is now the same type (no longer a separate unexported type), simply returning bd directly would suffice.

♻️ Simplified wrapper
 func calculateCostBreakdown(inputTokens, outputTokens, cachedTokens int, model string, serviceTier string) CostBreakdown {
-	bd := CalculateCostBreakdown(inputTokens, outputTokens, cachedTokens, model, serviceTier)
-	return CostBreakdown{
-		InputCost:                 bd.InputCost,
-		OutputCost:                bd.OutputCost,
-		CacheReadCost:             bd.CacheReadCost,
-		TotalCost:                 bd.TotalCost,
-		InputPricePerMToken:       bd.InputPricePerMToken,
-		OutputPricePerMToken:      bd.OutputPricePerMToken,
-		CacheReadPricePerMToken:   bd.CacheReadPricePerMToken,
-		ServiceTierCostMultiplier: bd.ServiceTierCostMultiplier,
-	}
+	return CalculateCostBreakdown(inputTokens, outputTokens, cachedTokens, model, serviceTier)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/billing.go` around lines 358 - 375, The calculateCostBreakdown
wrapper unnecessarily copies every field from the CostBreakdown returned by
CalculateCostBreakdown into a new identical CostBreakdown; change
calculateCostBreakdown to simply return the bd value from CalculateCostBreakdown
directly (i.e., return CalculateCostBreakdown(inputTokens, outputTokens,
cachedTokens, model, serviceTier)) and remove the manual field-by-field
construction, keeping the function name calculateCostBreakdown and the call to
CalculateCostBreakdown unchanged.
admin/handler.go (1)

636-655: 🏗️ Heavy lift

N+1 query pattern may cause timeouts with many accounts.

This loop issues up to 2 GetAccountBilledSince queries per account. With hundreds of accounts, this could exceed the 5-second context timeout and degrade admin API responsiveness.

Consider batching: fetch all account IDs with valid reset timestamps, then query billed sums in a single aggregate query grouped by account_id.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/handler.go` around lines 636 - 655, The current loop calls
h.db.GetAccountBilledSince per account (via acc.GetReset5hAt / acc.GetReset7dAt)
causing an N+1 query problem; instead gather all account IDs that have non-zero
GetReset5hAt and GetReset7dAt into two slices/maps, call a new batched DB method
(e.g. GetAccountsBilledSinceGrouped(ctx, []accountID, sinceTime) or a single
aggregate that accepts multiple (account_id, since) rows) to return billed sums
keyed by account_id, then iterate accounts and set accounts[i].Billed5h and
accounts[i].Billed7d from the returned map(s); update handler.go to replace
per-account GetAccountBilledSince calls with these batched calls and add the
corresponding DB interface/implementation changes in h.db.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@admin/handler.go`:
- Around line 686-698: The handler fetches acc via h.store.FindByID before
calling h.store.UpdateAccountCredit, then returns acc.CreditEnabled and
acc.CreditSkipUsageWindow which are stale; after a successful
UpdateAccountCredit(id, req.CreditEnabled, req.CreditSkipUsageWindow) call,
re-fetch the account (e.g., call h.store.FindByID(id) or otherwise obtain the
updated account) and use that updated object's CreditEnabled and
CreditSkipUsageWindow for the JSON response so the returned fields reflect the
applied changes.

In `@auth/fast_scheduler.go`:
- Around line 102-112: The else branch inside SetSchedulerMode has inconsistent
indentation: move the else and its inner sort.SliceStable block to align with
the matching if/else level used in SetSchedulerMode so formatting matches
surrounding code; locate the SetSchedulerMode function and the
sort.SliceStable(entries, ...) lambda (which references
entries[i].dispatchScore, entries[i].proven, entries[i].dbID) and adjust the
braces/indentation so the closing brace and the else keyword are at the same
indentation level as the corresponding if block.

In `@frontend/src/components/AccountUsageModal.tsx`:
- Around line 140-149: The credit switch buttons in AccountUsageModal.tsx (the
button that calls handleCreditToggle and uses creditEnabled/savingCredit) lack
accessible names and remove default focus styles; add an accessible label via
aria-label or aria-labelledby (e.g., aria-label="Enable credits" or link to a
visible label element) and restore visible keyboard focus by replacing
focus:outline-none with a visible focus style (e.g., focus:ring or focus-visible
styles) so keyboard users see focus; apply the same changes to the second switch
instance that mirrors lines 157-166.

In `@frontend/src/pages/ImageStudio.tsx`:
- Around line 816-825: The submit logic is using the component state
imageToImageMode directly which can be mutated by rerunFromJob before submitJob
runs; update submitJob to accept an explicit mode parameter (e.g., isEditMode)
and decide between createImageEditJob/createImageJob based on that local
parameter instead of reading imageToImageMode from state, and update
rerunFromJob to compute the correct isEditMode for the job it wants to rerun and
pass it into submitJob; additionally ensure rerunFromJob resets UI-only state
(call setImageToImageMode and clear/set input images via setInputImageDataURLs)
appropriately for non-edit reruns so the UI and payload remain consistent.
- Around line 437-471: handleImageFileChange reads files asynchronously and
snapshots inputImageDataURLs earlier, allowing race conditions to exceed
MAX_INPUT_IMAGES; fix by clamping inside the state updater: when calling
setInputImageDataURLs, use the functional form (prev => { const merged =
[...prev, ...dataURLs]; const allowed = merged.slice(0, MAX_INPUT_IMAGES); if
(allowed.length < merged.length) showToast(t('images.maxInputImages', { max:
MAX_INPUT_IMAGES }), 'error'); return allowed; }) so the limit is enforced
atomically regardless of overlapping uploads; reference MAX_INPUT_IMAGES,
inputImageDataURLs, setInputImageDataURLs and handleImageFileChange.

In `@README.zh-CN.md`:
- Line 528: Update the README zh-CN description for the `remaining_quota`
scheduling policy to remove ambiguity: change the phrase “优先使用用量的账号” to
“优先使用用量较低的账号”, so the full line reads “`remaining_quota` | 优先使用用量较低的账号;用量相同时轮询”,
ensuring the `remaining_quota` label clearly matches its intended behavior.

---

Outside diff comments:
In `@docs/API.md`:
- Around line 290-292: 示例模型列表中出现重复的 "gpt-5.5" 条目,导致误导调用方;请在 docs/API.md
中找到模型数组里重复的 { "id": "gpt-5.5", "object": "model", "owned_by": "openai"
}(第二个出现的条目),将其删除或替换为正确且唯一的模型 ID(例如改为 "gpt-5.4" 或其他真实模型 ID),确保最终模型列表中的 id
值唯一且与实际可用模型一致。

---

Nitpick comments:
In `@admin/handler.go`:
- Around line 636-655: The current loop calls h.db.GetAccountBilledSince per
account (via acc.GetReset5hAt / acc.GetReset7dAt) causing an N+1 query problem;
instead gather all account IDs that have non-zero GetReset5hAt and GetReset7dAt
into two slices/maps, call a new batched DB method (e.g.
GetAccountsBilledSinceGrouped(ctx, []accountID, sinceTime) or a single aggregate
that accepts multiple (account_id, since) rows) to return billed sums keyed by
account_id, then iterate accounts and set accounts[i].Billed5h and
accounts[i].Billed7d from the returned map(s); update handler.go to replace
per-account GetAccountBilledSince calls with these batched calls and add the
corresponding DB interface/implementation changes in h.db.

In `@admin/image_studio.go`:
- Around line 337-340: The handler currently only checks req.InputImages
non-empty but defers max-count validation to buildAdminImageEditRequest, causing
jobs to be created then fail asynchronously; update the handler (the function
handling the request in admin/image_studio.go) to also validate
len(req.InputImages) <= MaxImageEditInputCount before creating the job and call
writeError(c, http.StatusBadRequest, ...) when exceeded, mirroring the
validation performed in buildAdminImageEditRequest so failures are returned
synchronously; reference req.InputImages, MaxImageEditInputCount,
buildAdminImageEditRequest, and writeError when locating where to add the early
check.

In `@database/billing.go`:
- Around line 358-375: The calculateCostBreakdown wrapper unnecessarily copies
every field from the CostBreakdown returned by CalculateCostBreakdown into a new
identical CostBreakdown; change calculateCostBreakdown to simply return the bd
value from CalculateCostBreakdown directly (i.e., return
CalculateCostBreakdown(inputTokens, outputTokens, cachedTokens, model,
serviceTier)) and remove the manual field-by-field construction, keeping the
function name calculateCostBreakdown and the call to CalculateCostBreakdown
unchanged.

In `@database/postgres.go`:
- Around line 3367-3395: UpdateAccountCredit currently executes the UPDATE
without checking whether any row was modified, so updates targeting non-existent
accounts are silently ignored; modify UpdateAccountCredit to inspect the
sql.Result from db.conn.ExecContext (via RowsAffected) and if RowsAffected == 0
return sql.ErrNoRows (matching behavior in SetAccountEnabled), otherwise return
nil or the Exec error—use the existing variables (query, args) and handle the
result before returning.

In `@docs/API.md`:
- Around line 484-488: Update the parameter table so the description for
credit_skip_usage_window clearly states it only applies when credit_enabled is
true; modify the row for credit_skip_usage_window to add a parenthetical or
sentence like "仅在 credit_enabled=true 时生效" (or the English equivalent) and, if
helpful, add a short note to the credit_enabled row indicating that related
credit fields (e.g., credit_skip_usage_window) depend on it.

In `@docs/CONFIGURATION.md`:
- Line 46: Update the BIND_HOST environment variable description to clarify it
is the Docker/container port publish bind address (used by Docker/compose to
bind host network interfaces) and not the application listen address; explicitly
mention CODEX_BIND as the application listen address (socket/interface the app
binds to inside the container) so readers won’t confuse the two (edit the table
row referencing BIND_HOST and add a short note referencing CODEX_BIND for
contrast).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40383735-2e43-4e60-aa96-8c87e9b398cb

📥 Commits

Reviewing files that changed from the base of the PR and between 18de86a and 5990824.

📒 Files selected for processing (31)
  • .env.example
  • .env.sqlite.example
  • README.md
  • README.zh-CN.md
  • admin/handler.go
  • admin/image_studio.go
  • admin/oauth.go
  • auth/fast_scheduler.go
  • auth/fast_scheduler_test.go
  • auth/store.go
  • database/billing.go
  • database/billing_test.go
  • database/postgres.go
  • database/sqlite.go
  • docker-compose.sqlite.local.yml
  • docker-compose.sqlite.yml
  • docs/API.md
  • docs/CHANGELOG.md
  • docs/CONFIGURATION.md
  • frontend/src/api.ts
  • frontend/src/components/AccountUsageModal.tsx
  • frontend/src/locales/en.json
  • frontend/src/locales/zh.json
  • frontend/src/pages/Accounts.tsx
  • frontend/src/pages/ImageStudio.tsx
  • frontend/src/pages/Settings.tsx
  • frontend/src/types.ts
  • proxy/admin_images.go
  • proxy/executor.go
  • proxy/images.go
  • proxy/model_registry.go

Comment thread admin/handler.go Outdated
Comment thread auth/fast_scheduler.go Outdated
Comment thread frontend/src/components/AccountUsageModal.tsx
Comment thread frontend/src/pages/ImageStudio.tsx
Comment thread frontend/src/pages/ImageStudio.tsx Outdated
Comment thread README.zh-CN.md Outdated
@DeliciousBuding DeliciousBuding changed the title codex2api v2 重大更新——10 项改进,覆盖定价、调度、图生图、成本与安全 feat: billing correction, credit quota, scheduler modes, image-to-image, 5h/7d cost, and codex-auto-review May 20, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR delivers a v2 feature sweep across billing/pricing, scheduling behavior, admin image tooling (incl. image-to-image), and operational hardening (SQLite Docker port binding + OAuth proxy fallback). It extends the backend scheduler and billing engine, surfaces new per-account fields in the admin UI/API, and updates documentation accordingly.

Changes:

  • Expand/adjust billing rules (GPT-5.5(+pro), long-context premiums, cache-read pricing) and expose cost breakdowns/tests.
  • Add scheduler_mode (round_robin / remaining_quota) + per-account credit flags, with admin API + UI support.
  • Add admin image-to-image jobs and improve image request retry/error handling; harden SQLite compose port binding defaults.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
README.zh-CN.md Documents new scheduler modes, credit flags, billing tips, OAuth PKCE, image-to-image, SQLite bind defaults.
README.md English README updates for scheduler modes, credit flags, pricing notes, OAuth PKCE, image-to-image, SQLite bind defaults.
proxy/model_registry.go Registers codex-auto-review as a disabled builtin model with notes.
proxy/images.go Adds image retry caps, edit input limits, base64 decode guards, and stream retry/error logging adjustments.
proxy/executor.go Changes pooled HTTP client timeout behavior.
proxy/admin_images.go Adds in-process Images Edits execution helper for admin image studio edit jobs.
frontend/src/types.ts Adds billed window fields + credit flags + scheduler_mode + image edit payload input_images typing.
frontend/src/pages/Settings.tsx Adds scheduler mode selector to settings UI.
frontend/src/pages/ImageStudio.tsx Adds text-to-image vs image-to-image mode and reference image upload handling.
frontend/src/pages/Accounts.tsx Adds per-account billed (5h/7d) column rendering.
frontend/src/locales/zh.json Adds zh translations for credit settings, billed, image-to-image UI strings, scheduler mode.
frontend/src/locales/en.json Adds en translations for credit settings, billed, image-to-image UI strings, scheduler mode.
frontend/src/components/AccountUsageModal.tsx Adds per-account credit flag toggles in the usage modal.
frontend/src/api.ts Adds PATCH account credit endpoint + image edit job creation endpoint.
docs/CONFIGURATION.md Documents BIND_HOST, CODEX_ALLOW_ANONYMOUS, scheduler mode, and credit flags.
docs/CHANGELOG.md Adds a May 2026 v2 changelog entry summarizing the release.
docs/API.md Documents new fields (credit + billed windows) and new admin endpoints (credit + image edit jobs).
docker-compose.sqlite.yml Binds SQLite compose ports to 127.0.0.1 by default (override via BIND_HOST).
docker-compose.sqlite.local.yml Same localhost-only bind default for local SQLite compose.
database/sqlite.go Adds scheduler_mode to system settings and credit columns to accounts (SQLite migrations).
database/postgres.go Adds credit columns, system setting scheduler_mode, billed aggregation helper, and scan/select updates.
database/billing.go Expands pricing rules, adds long-context pricing, exports pricing/cost breakdown APIs.
database/billing_test.go Updates/extends billing tests for new pricing rules and exported APIs.
auth/store.go Adds scheduler_mode plumbing, credit behavior in scheduling penalties, and store update methods.
auth/fast_scheduler.go Adds remaining_quota mode logic and resorting on mode change.
auth/fast_scheduler_test.go Adds/updates tests for scheduler mode behavior.
admin/oauth.go Adds system proxy fallback when per-request/session proxy URL is absent.
admin/image_studio.go Adds admin image edit job endpoint and execution path via proxy image edits.
admin/handler.go Exposes credit + billed fields; adds PATCH credit route; exposes scheduler_mode in settings API.
.env.sqlite.example Documents SQLite compose localhost binding and BIND_HOST override.
.env.example Documents standard compose bind expectations and BIND_HOST override.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread proxy/executor.go
client: &http.Client{
Transport: transport,
Timeout: 0,
Timeout: 10 * time.Minute,
Comment thread auth/fast_scheduler.go
Comment on lines +279 to +283
cursor := &s.cursors[tierIdx]
if s.schedulerMode == "remaining_quota" {
cursor = &zeroCursor
}
acc, stale := s.scanRangeLocked(tier, 0, len(bucket), cursor, baseLimit, now, apiKeyID, exclude, filter)
Comment thread admin/image_studio.go
if len(req.InputImages) == 0 {
writeError(c, http.StatusBadRequest, "图生图需要上传参考图片")
return
}
Comment thread admin/handler.go
Comment on lines +642 to +646
if t := acc.GetReset5hAt(); !t.IsZero() {
billed, err := h.db.GetAccountBilledSince(ctx, accounts[i].ID, t.Add(-5*time.Hour))
if err == nil {
accounts[i].Billed5h = &billed
}
Comment thread admin/handler.go Outdated
Comment on lines +681 to +684
if err := json.NewDecoder(c.Request.Body).Decode(&req); err != nil {
writeError(c, http.StatusBadRequest, "请求格式错误")
return
}
</Field>

<div className="flex items-center gap-2">
<span className="text-xs font-semibold text-muted-foreground">{t('images.mode')}:</span>
…tation, race conditions, and docs

- handler: re-fetch account after UpdateAccountCredit, add DisallowUnknownFields
- fast_scheduler: fix } else { indentation (was nested inside if body)
- AccountUsageModal: add aria-label, focus-visible styles, disabled cursor state
- ImageStudio: clamp input images in setter, decouple rerun from stale UI state
- billing: simplify calculateCostBreakdown wrapper
- postgres: return sql.ErrNoRows when UpdateAccountCredit affects zero rows
- image_studio: validate input image count before job creation
- docs: fix duplicate model ID, clarify BIND_HOST and credit_skip_usage_window
- README.zh-CN: fix remaining_quota description ambiguity
- locales: add missing images.mode key

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
admin/image_studio.go (1)

337-344: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject blank input_images entries before queueing the job.

The current guard only checks slice length, so ["", " "] passes validation and creates a job that can only fail later when buildAdminImageEditRequest sends empty image_url values upstream. Trim/filter each entry first and return 400 if nothing valid remains.

💡 Minimal fix
 	if len(req.InputImages) == 0 {
 		writeError(c, http.StatusBadRequest, "图生图需要上传参考图片")
 		return
 	}
+	normalizedImages := make([]string, 0, len(req.InputImages))
+	for _, raw := range req.InputImages {
+		raw = strings.TrimSpace(raw)
+		if raw != "" {
+			normalizedImages = append(normalizedImages, raw)
+		}
+	}
+	req.InputImages = normalizedImages
+	if len(req.InputImages) == 0 {
+		writeError(c, http.StatusBadRequest, "图生图需要上传参考图片")
+		return
+	}
 	if len(req.InputImages) > proxy.MaxImageEditInputCount {
 		writeError(c, http.StatusBadRequest, fmt.Sprintf("参考图片数量超过限制 (%d, 最多 %d)", len(req.InputImages), proxy.MaxImageEditInputCount))
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/image_studio.go` around lines 337 - 344, The validation currently only
checks len(req.InputImages) but allows blank/whitespace entries like ["", "  
"]; before the existing length checks and before queueing the job (in
admin/image_studio.go), trim and filter req.InputImages to remove
empty/whitespace-only strings (e.g., build a filtered slice), then use that
filtered slice for subsequent checks against proxy.MaxImageEditInputCount and
when calling buildAdminImageEditRequest; if the filtered slice is empty, return
writeError(c, http.StatusBadRequest, "图生图需要上传参考图片") and if it exceeds
proxy.MaxImageEditInputCount return the existing formatted error.
database/billing.go (1)

125-139: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Return a copy from GetModelPricing instead of a shared global pointer.

Now that this helper is exported, callers can accidentally mutate the package-level pricing table through the returned pointer and change billing for the whole process. Returning a copied struct preserves the read API without exposing mutable global state.

💡 Minimal fix
 func GetModelPricing(model string) *ModelPricing {
 	normalized := normalizeBillingModelName(model)
 	if pricing := claudeFamilyPricing(normalized); pricing != nil {
-		return pricing
+		copy := *pricing
+		return &copy
 	}
 	if pricing := geminiFamilyPricing(normalized); pricing != nil {
-		return pricing
+		copy := *pricing
+		return &copy
 	}
 	if codexModel, ok := normalizeCodexBillingModel(normalized); ok {
 		normalized = codexModel
 	}
 	if pricing := modelRulePricing(normalized); pricing != nil {
-		return pricing
+		copy := *pricing
+		return &copy
 	}
-	return defaultModelPricing
+	copy := *defaultModelPricing
+	return &copy
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/billing.go` around lines 125 - 139, GetModelPricing currently
returns pointers to package-level ModelPricing values which allows callers to
mutate shared state; update it to return a copy instead. Modify GetModelPricing
so that whenever it intends to return a non-nil pricing (results from
claudeFamilyPricing, geminiFamilyPricing, modelRulePricing or
defaultModelPricing), it returns a distinct copy (e.g., copy the struct value
and return a pointer to that copy) or change the function signature to return a
ModelPricing value and return copies directly; keep the same lookup flow
(normalizeBillingModelName, normalizeCodexBillingModel, claudeFamilyPricing,
geminiFamilyPricing, modelRulePricing) but ensure the returned object is not the
original package-level pointer.
proxy/executor.go (1)

201-205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the 10-minute timeout from pooled streaming clients.

http.Client.Timeout applies to the entire request-response cycle, including body reading. For pooled Codex/OpenAI clients that stream SSE responses, this means any stream exceeding 10 minutes gets aborted mid-response, causing the same stream-cutoff failures this change aims to reduce. Remove the timeout here and rely on request contexts (which callers already provide via newDrainableUpstreamContext) or set deadlines at non-streaming call sites instead.

Minimal fix
 	entry := &poolEntry{
 		client: &http.Client{
 			Transport: transport,
-			Timeout:   10 * time.Minute,
 		},
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@proxy/executor.go` around lines 201 - 205, The pooled streaming client
creation sets http.Client.Timeout to 10*time.Minute which aborts long SSE
streams; remove the Timeout field from the http.Client in the poolEntry
initialization so pooled clients rely on caller-provided contexts (e.g.,
newDrainableUpstreamContext) or set per-call deadlines at non-streaming sites;
update the client creation where poolEntry and its client with Transport:
transport are constructed to omit the Timeout assignment.
admin/handler.go (1)

688-705: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t gate credit updates on runtime-store presence.

ListAccounts returns rows from h.db.ListActive(...) even when there is no matching accountMap entry, so an admin can see a valid account that h.store.FindByID(id) still returns nil for. This handler will 404 that account before attempting the update, and the post-update response has the same blind spot. Use the DB as the existence/source-of-truth lookup here instead of the runtime pool.

🐛 Proposed fix
-	acc := h.store.FindByID(id)
-	if acc == nil {
-		writeError(c, http.StatusNotFound, "账号不存在")
-		return
-	}
+	row, err := h.db.GetAccountByID(c.Request.Context(), id)
+	if err != nil {
+		if errors.Is(err, sql.ErrNoRows) {
+			writeError(c, http.StatusNotFound, "账号不存在")
+			return
+		}
+		writeInternalError(c, err)
+		return
+	}

 	// 传入 *bool:nil = 不修改该字段
 	if err := h.store.UpdateAccountCredit(id, req.CreditEnabled, req.CreditSkipUsageWindow); err != nil {
 		writeError(c, http.StatusInternalServerError, "更新信用设置失败: "+err.Error())
 		return
 	}

-	acc = h.store.FindByID(id)
-	if acc != nil {
-		c.JSON(http.StatusOK, gin.H{"message": "信用设置已更新", "credit_enabled": acc.CreditEnabled, "credit_skip_usage_window": acc.CreditSkipUsageWindow})
-	} else {
-		c.JSON(http.StatusOK, gin.H{"message": "信用设置已更新"})
-	}
+	row, err = h.db.GetAccountByID(c.Request.Context(), id)
+	if err == nil {
+		c.JSON(http.StatusOK, gin.H{
+			"message":                  "信用设置已更新",
+			"credit_enabled":           row.CreditEnabled,
+			"credit_skip_usage_window": row.CreditSkipUsageWindow,
+		})
+		return
+	}
+	c.JSON(http.StatusOK, gin.H{"message": "信用设置已更新"})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@admin/handler.go` around lines 688 - 705, The handler currently gates
existence on the runtime store via h.store.FindByID which causes valid DB
accounts to 404; change the existence and post-update response to use the
persistent DB lookup instead of the runtime pool (use your DB lookup method —
e.g., h.db.FindByID / h.db.GetAccount or whichever exists) to check the account
and to fetch credit fields after calling h.store.UpdateAccountCredit(id, ...);
remove the initial h.store.FindByID nil-return 404 check and instead 1) verify
existence with the DB, 2) call h.store.UpdateAccountCredit with
req.CreditEnabled/req.CreditSkipUsageWindow, and 3) reload the account from the
DB to populate credit_enabled and credit_skip_usage_window in the JSON response.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auth/fast_scheduler.go`:
- Around line 279-281: The code resets zeroCursor to 0 on every acquire when
s.schedulerMode == "remaining_quota", which causes scans to always start at
bucket 0 and hotspot the first account; change this to maintain a persistent
per-tier cursor instead of resetting zeroCursor each call—replace the
zeroCursor.Store(0) / cursor = &zeroCursor pattern with a persistent atomic
cursor per tier (e.g., a map from tier -> atomic.Int64 or sync/atomic value) and
rotate it (atomic increment with wrap modulo bucket count) so each acquire picks
up the next starting index within the lowest-usage cohort, ensuring even
distribution.

In `@database/billing_test.go`:
- Around line 297-301: The test currently repeats the lowercase entry and
therefore doesn't exercise case-insensitive normalization; update the third
element in the test data (the duplicate {"codex-auto-review", "gpt-5.4"}) to a
differently-cased variant such as {"Codex-Auto-Review", "gpt-5.4"} (or
"CODEX-AUTO-REVIEW") so the slice of inputs (the entries containing
{"codex-auto-review", ...}, {"codex-auto-review-v2", ...}, {"codex-auto-review",
...}, {"codex_auto_review", ...}) properly verifies case-insensitive
normalization in the test.

In `@frontend/src/pages/ImageStudio.tsx`:
- Around line 811-826: The submitJob function can mis-detect edit mode when
createJobPayload omits input_images; change submitJob to require forceMode for
all calls (make forceMode non-optional: forceMode: 'text' | 'edit'), compute
isEditMode as forceMode === 'edit', run the input_images presence check and
prompt validation based on that, and route to api.createImageEditJob or
api.createImageJob accordingly; update all callers of submitJob/Generate button
to pass the explicit forceMode value so the required-image check cannot be
bypassed.

---

Outside diff comments:
In `@admin/handler.go`:
- Around line 688-705: The handler currently gates existence on the runtime
store via h.store.FindByID which causes valid DB accounts to 404; change the
existence and post-update response to use the persistent DB lookup instead of
the runtime pool (use your DB lookup method — e.g., h.db.FindByID /
h.db.GetAccount or whichever exists) to check the account and to fetch credit
fields after calling h.store.UpdateAccountCredit(id, ...); remove the initial
h.store.FindByID nil-return 404 check and instead 1) verify existence with the
DB, 2) call h.store.UpdateAccountCredit with
req.CreditEnabled/req.CreditSkipUsageWindow, and 3) reload the account from the
DB to populate credit_enabled and credit_skip_usage_window in the JSON response.

In `@admin/image_studio.go`:
- Around line 337-344: The validation currently only checks len(req.InputImages)
but allows blank/whitespace entries like ["", "   "]; before the existing length
checks and before queueing the job (in admin/image_studio.go), trim and filter
req.InputImages to remove empty/whitespace-only strings (e.g., build a filtered
slice), then use that filtered slice for subsequent checks against
proxy.MaxImageEditInputCount and when calling buildAdminImageEditRequest; if the
filtered slice is empty, return writeError(c, http.StatusBadRequest,
"图生图需要上传参考图片") and if it exceeds proxy.MaxImageEditInputCount return the
existing formatted error.

In `@database/billing.go`:
- Around line 125-139: GetModelPricing currently returns pointers to
package-level ModelPricing values which allows callers to mutate shared state;
update it to return a copy instead. Modify GetModelPricing so that whenever it
intends to return a non-nil pricing (results from claudeFamilyPricing,
geminiFamilyPricing, modelRulePricing or defaultModelPricing), it returns a
distinct copy (e.g., copy the struct value and return a pointer to that copy) or
change the function signature to return a ModelPricing value and return copies
directly; keep the same lookup flow (normalizeBillingModelName,
normalizeCodexBillingModel, claudeFamilyPricing, geminiFamilyPricing,
modelRulePricing) but ensure the returned object is not the original
package-level pointer.

In `@proxy/executor.go`:
- Around line 201-205: The pooled streaming client creation sets
http.Client.Timeout to 10*time.Minute which aborts long SSE streams; remove the
Timeout field from the http.Client in the poolEntry initialization so pooled
clients rely on caller-provided contexts (e.g., newDrainableUpstreamContext) or
set per-call deadlines at non-streaming sites; update the client creation where
poolEntry and its client with Transport: transport are constructed to omit the
Timeout assignment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5dd9034b-2b79-414b-91ed-826d1cc30c70

📥 Commits

Reviewing files that changed from the base of the PR and between 5990824 and 78fe653.

📒 Files selected for processing (16)
  • README.zh-CN.md
  • admin/handler.go
  • admin/image_studio.go
  • auth/fast_scheduler.go
  • database/billing.go
  • database/billing_test.go
  • database/postgres.go
  • docs/API.md
  • docs/CHANGELOG.md
  • docs/CONFIGURATION.md
  • frontend/src/components/AccountUsageModal.tsx
  • frontend/src/locales/en.json
  • frontend/src/locales/zh.json
  • frontend/src/pages/ImageStudio.tsx
  • proxy/executor.go
  • proxy/model_registry.go
✅ Files skipped from review due to trivial changes (4)
  • docs/CHANGELOG.md
  • docs/CONFIGURATION.md
  • README.zh-CN.md
  • frontend/src/locales/en.json

Comment thread auth/fast_scheduler.go
Comment on lines +279 to +281
if s.schedulerMode == "remaining_quota" {
zeroCursor.Store(0)
cursor = &zeroCursor
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

remaining_quota now hot-spots the first account in each tier.

Because zeroCursor is reset to 0 on every acquire, scans always start from bucket index 0. With usage snapshots only changing on updates/rebuilds, equal-usage accounts never rotate and the head entry absorbs traffic until it fills up. Keep a persistent cursor per tier, or at least rotate within the lowest-usage cohort, so this mode still spreads load instead of pinning to the first row.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/fast_scheduler.go` around lines 279 - 281, The code resets zeroCursor to
0 on every acquire when s.schedulerMode == "remaining_quota", which causes scans
to always start at bucket 0 and hotspot the first account; change this to
maintain a persistent per-tier cursor instead of resetting zeroCursor each
call—replace the zeroCursor.Store(0) / cursor = &zeroCursor pattern with a
persistent atomic cursor per tier (e.g., a map from tier -> atomic.Int64 or
sync/atomic value) and rotate it (atomic increment with wrap modulo bucket
count) so each acquire picks up the next starting index within the lowest-usage
cohort, ensuring even distribution.

Comment thread database/billing_test.go
Comment thread frontend/src/pages/ImageStudio.tsx
- normalizeCodexBillingModel: lowercase input for case-insensitive matching
- billing_test: use uppercase CODEX-AUTO-REVIEW to actually test case insensitivity
- ImageStudio: pass explicit forceMode from Generate button to prevent
  image-to-image mode silently falling through to text endpoint

Co-Authored-By: Claude <noreply@anthropic.com>
@james-6-23 james-6-23 merged commit 0627e0b into james-6-23:main May 20, 2026
6 checks passed
@james-6-23
Copy link
Copy Markdown
Owner

https://linux.do/u/kkkyyx/summary 佬私信我留个联系方式😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment