Skip to content

feat: add codex priority billing source setting#960

Merged
ding113 merged 2 commits into
devfrom
feat/codex-priority-billing-source
Mar 21, 2026
Merged

feat: add codex priority billing source setting#960
ding113 merged 2 commits into
devfrom
feat/codex-priority-billing-source

Conversation

@ding113

@ding113 ding113 commented Mar 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a system setting to choose Codex Priority billing by requested or actual service tier, defaulting to requested
  • Route the selected billing source through response handling, special setting audit data, settings UI, and migration wiring
  • Harden old-database compatibility by preserving existing settings when the new column is missing and degrading saves/init paths gracefully

Problem

Some third-party Codex proxies do not return the service_tier field in responses, causing incorrect billing for Fast Priority requests. Previously, CCH always used the response's service_tier for billing decisions, which meant:

  • Requests asking for "priority" but receiving downgraded responses were not charged the priority rate
  • Users had no control over whether to bill based on what was requested vs. what was actually delivered

Related Issues:

Solution

Adds a new system setting codexPriorityBillingSource with two modes:

  • requested (default): Bill based on the service_tier sent in the request - ensures priority pricing when user asks for priority
  • actual: Bill based on the service_tier returned in the response, falling back to request value if response omits it

Changes

Core Changes

  • Database Migration (drizzle/0085_busy_ken_ellis.sql): Adds codex_priority_billing_source column to system_settings table
  • Session (src/app/v1/_lib/proxy/session.ts): New getCodexPriorityBillingSource() method with caching and fallback logic
  • Response Handler (src/app/v1/_lib/proxy/response-handler.ts): Refactored billing decision logic to resolveCodexPriorityBillingDecision() which respects the new setting
  • Special Settings (src/types/special-settings.ts, src/lib/utils/special-settings.ts): Extended CodexServiceTierResultSpecialSetting to track billingSourcePreference and resolvedFrom for audit transparency

Supporting Changes

  • Types (src/types/system-config.ts): Added CodexPriorityBillingSource type
  • Validation (src/lib/validation/schemas.ts): Zod schema for the new setting
  • Repository (src/repository/system-config.ts): Database compatibility layer with graceful degradation for missing columns
  • UI (src/app/[locale]/settings/config/): New dropdown in system settings form
  • i18n: Translations for all 5 languages (en, zh-CN, zh-TW, ja, ru)

Backward Compatibility

  • Existing installations without the new column will continue to work with requested as the implicit default
  • getSystemSettings() attempts full selection, falls back to selection without new column, then to minimal fields
  • updateSystemSettings() gracefully handles missing column by excluding it from updates

Testing

Automated Tests

  • Unit tests added/updated in tests/unit/proxy/session.test.ts
  • Unit tests for system config save (tests/unit/actions/system-config-save.test.ts)
  • Unit tests for special settings (tests/unit/lib/utils/special-settings.test.ts)
  • Unit tests for missing columns handling (tests/unit/repository/system-config-update-missing-columns.test.ts)
  • Integration tests for billing model source (tests/integration/billing-model-source.test.ts) - includes new test cases for "actual" mode

Test Coverage Highlights

  • requested mode: Uses priority pricing when request has service_tier: "priority", ignores actual response tier
  • actual mode: Uses priority pricing when response reports service_tier: "priority", falls back to request value if response omits it
  • actual mode: Does not use priority pricing when request asked for priority but response explicitly reports non-priority
  • Cache fallback: When direct settings read fails, uses cached system settings

Verification

  • bun run typecheck
  • bun run build
  • bun run lint
  • bun run test
  • bunx vitest run tests/integration/billing-model-source.test.ts tests/unit/lib/utils/special-settings.test.ts tests/unit/actions/system-config-save.test.ts tests/unit/lib/config/system-settings-cache.test.ts tests/unit/repository/system-config-update-missing-columns.test.ts tests/unit/proxy/session.test.ts

Notes

  • Lint still reports pre-existing Biome schema/version drift and unrelated existing warnings outside this change set
  • Fast badge/effectivePriority intentionally follows the final billing semantics for this feature while retaining requested/actual raw fields in audit data

Description enhanced by Claude AI

Greptile Summary

This PR introduces a new system setting codexPriorityBillingSource that lets operators choose whether Codex Priority billing decisions are based on the service tier sent in the request (requested, default) or the service tier returned in the response (actual). This solves a real compatibility problem with third-party Codex proxies that omit service_tier from responses.

Key changes:

  • DB migration (drizzle/0085_busy_ken_ellis.sql): Adds codex_priority_billing_source varchar(20) DEFAULT 'requested' NOT NULL — safe to apply on live databases.
  • Billing resolution (response-handler.ts): New resolveCodexPriorityBillingDecision() replaces the old direct actualServiceTier === "priority" check; called consistently across all three finalization paths (streaming, non-streaming, deferred).
  • Session caching (session.ts): getCodexPriorityBillingSource() reuses the existing billingSettingsPromise coalescing pattern, loading both billing settings in a single DB read per request.
  • Backward compatibility (system-config.ts, transformers.ts): Three-tier read fallback and two-tier write fallback gracefully handle databases where the new column hasn't been migrated yet; toSystemSettings defaults to "requested" for missing/unknown values.
  • Audit trail (special-settings.ts): CodexServiceTierResultSpecialSetting gains optional billingSourcePreference and resolvedFrom fields that record exactly which source was used for each request, with backward-compatible display logic for old stored records.
  • UI + i18n: New dropdown in the system settings form, translations for all 5 supported locales.

Confidence Score: 5/5

  • Safe to merge — no functional bugs found; the feature is correctly wired end-to-end with comprehensive tests and solid backward-compatibility handling.
  • All three finalization paths in the response handler use the same billing decision logic. Fallback chains are consistent (DB → cache → default "requested") across session, cache, transformer, and repository layers. Graceful degradation for pre-migration databases is tested and functional. The two non-blocking P2 suggestions (redundant backward-compat branch, dead try/catch) have no correctness impact.
  • No files require special attention.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/response-handler.ts New resolveCodexPriorityBillingDecision() function correctly handles both billing modes and wires effectivePriority into three call sites (streaming, non-streaming, deferred finalization). Minor: dead try/catch around getCodexPriorityBillingSource().
src/app/v1/_lib/proxy/session.ts New getCodexPriorityBillingSource() correctly reuses the billingSettingsPromise coalescing pattern for thread-safe single-load-per-request semantics; fallback chain (live → cache → default "requested") is consistent.
src/repository/system-config.ts Three-tier graceful degradation for reads and a two-tier fallback for writes correctly handle the missing-column case; downgraded update removes codexPriorityBillingSource from both SET and RETURNING, and the column's absence is transparently resolved to the default value by toSystemSettings.
src/lib/utils/special-settings.ts New billingSourcePreference/resolvedFrom fields in buildSettingKey and hasPriorityServiceTierSpecialSetting are correct; backward-compat branch in hasPriorityServiceTierSpecialSetting is redundant (old records' effectivePriority already encodes the same value) but harmless.
src/lib/config/system-settings-cache.ts Correctly adds codexPriorityBillingSource: "requested" to both the DEFAULT_SETTINGS pick and the hard-coded emergency fallback object; invalidateSystemSettingsCache() is called from the save action, ensuring fresh values propagate.
src/types/special-settings.ts New optional fields billingSourcePreference? and resolvedFrom? on CodexServiceTierResultSpecialSetting use correct optional-nullable typing for backward compatibility with old serialized records.
drizzle/0085_busy_ken_ellis.sql Simple, correct ALTER TABLE ADD COLUMN with DEFAULT 'requested' NOT NULL; safely applicable to live databases without locking concerns on small settings tables.
src/repository/_shared/transformers.ts toSystemSettings correctly validates codexPriorityBillingSource against the allowed enum values and defaults to "requested" for any unexpected/missing value, matching all other fallback paths.
src/lib/validation/schemas.ts New codexPriorityBillingSource Zod enum schema is correctly optional and uses a clear error message; wired into UpdateSystemSettingsSchema.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ResponseHandler
    participant Session
    participant DB as SystemSettings DB
    participant Cache as InMemoryCache

    Client->>ResponseHandler: Codex response received
    ResponseHandler->>ResponseHandler: parseServiceTierFromResponseText()<br/>(actualServiceTier)
    ResponseHandler->>ResponseHandler: resolveCodexPriorityBillingDecision(session, actualServiceTier)
    ResponseHandler->>Session: getCodexPriorityBillingSource()
    Session->>Session: loadBillingSettings() [promise-coalesced]
    alt Live DB read succeeds
        Session->>DB: getSystemSettings()
        DB-->>Session: codexPriorityBillingSource
    else DB fails, cache available
        Session->>Cache: getCachedSystemSettingsOnlyCache()
        Cache-->>Session: cachedSettings.codexPriorityBillingSource
    else All fails
        Session-->>Session: default "requested"
    end
    Session-->>ResponseHandler: "requested" | "actual"

    alt billingSourcePreference = "actual"
        alt actualServiceTier != null
            ResponseHandler->>ResponseHandler: effectiveTier = actualServiceTier<br/>resolvedFrom = "actual"
        else actualServiceTier null, requestedServiceTier set
            ResponseHandler->>ResponseHandler: effectiveTier = requestedServiceTier<br/>resolvedFrom = "requested"
        end
    else billingSourcePreference = "requested"
        ResponseHandler->>ResponseHandler: effectiveTier = requestedServiceTier<br/>resolvedFrom = "requested"
    end

    ResponseHandler->>ResponseHandler: effectivePriority = (effectiveTier === "priority")
    ResponseHandler->>Session: addSpecialSetting(codex_service_tier_result)
    ResponseHandler->>ResponseHandler: calculateRequestCost(priorityServiceTierApplied)
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/lib/utils/special-settings.ts
Line: 212-219

Comment:
**Redundant backward-compat branch in `hasPriorityServiceTierSpecialSetting`**

The backward-compatibility guard (lines 212–218) is logically redundant. For old records (before this PR), `effectivePriority` was always computed as `actualServiceTier === "priority"`, so falling through to `return codexServiceTierResult.effectivePriority` on line 219 would produce the exact same result. The extra branch adds complexity without changing behavior. Consider simplifying to just return `effectivePriority` unconditionally:

```suggestion
    return codexServiceTierResult.effectivePriority;
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-handler.ts
Line: 234-243

Comment:
**Dead catch block around `getCodexPriorityBillingSource()`**

`session.getCodexPriorityBillingSource()` never throws — `loadBillingSettings()` catches all errors internally and returns a default value. The `try/catch` wrapping it is dead code, since exceptions are swallowed inside the method. Consider removing the try/catch to reduce noise, or adding a comment explaining why it's kept for future safety:

```suggestion
  billingSourcePreference = await session.getCodexPriorityBillingSource();
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "fix: address codex p..."

Greptile also left 2 inline comments on this PR.

@coderabbitai

coderabbitai Bot commented Mar 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

本 PR 在系统设置中新增 codexPriorityBillingSource"requested" | "actual"),包含数据库迁移、类型与验证、缓存与仓库读写、代理/计费决策、前端表单、本地化和大量测试更新以支持优先级计费来源决策与回退逻辑。

Changes

Cohort / File(s) Summary
数据库迁移与模式
drizzle/0085_busy_ken_ellis.sql, drizzle/meta/_journal.json, src/drizzle/schema.ts
新增列 codex_priority_billing_source varchar(20),默认 'requested',并更新 migration 日志与 Drizzle 模式声明。
类型与验证
src/types/system-config.ts, src/types/special-settings.ts, src/lib/validation/schemas.ts
新增导出类型 `CodexPriorityBillingSource = "requested"
仓库、转换与缓存
src/repository/system-config.ts, src/repository/_shared/transformers.ts, src/lib/config/system-settings-cache.ts, src/lib/config/index.ts
仓库读写/插入/更新流程加入新字段,添加对缺失列(42703) 的降级重试路径;transformer 映射并默认为 "requested";缓存 API 新增 getCachedSystemSettingsOnlyCache 并将默认设置扩展为包含该字段。
代理会话与计费决策
src/app/v1/_lib/proxy/session.ts, src/app/v1/_lib/proxy/response-handler.ts, src/lib/utils/special-settings.ts
Session 缓存拓展以加载/缓存两项计费设置并加入两阶段回退;引入 getCodexPriorityBillingSource()resolveCodexPriorityBillingDecision(),并将响应处理与计费/成本更新改为基于会话解析的决策;special-settings 的去重键与优先级判定逻辑更新以包含审计字段。
前端 / API / 操作
src/actions/system-config.ts, src/app/[locale]/settings/config/_components/system-settings-form.tsx, src/app/[locale]/settings/config/page.tsx, src/app/api/admin/system-config/route.ts
表单与 action 支持新字段(状态、Select 控件、提交时随 payload 发送);API 路由在更新成功后调用 invalidateSystemSettingsCache() 并将 codexPriorityBillingSource 传入 updateSystemSettings
本地化文案
messages/en/settings/config.json, messages/ja/settings/config.json, messages/ru/settings/config.json, messages/zh-CN/settings/config.json, messages/zh-TW/settings/config.json
为多语言新增表单标签、描述、占位符与选项(requested / actual)键以供 UI 使用。
测试变更
tests/integration/billing-model-source.test.ts, tests/unit/.../*.test.ts, tests/unit/repository/system-config-update-missing-columns.test.ts
大量单元与集成测试更新/新增:覆盖新字段默认值、缓存失效/回退、仓库的缺列降级路径,以及 Codex requested/actual 模式下的计费行为与成本断言。

代码评审工作量估算

🎯 4 (复杂) | ⏱️ ~45 分钟

可能相关的 PR

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题准确概括了主要变更——添加Codex优先级计费来源设置,简洁明确地反映了核心功能。
Description check ✅ Passed 拉取请求描述详细说明了新功能的目的、解决的问题、实现方案及所有关键改动,与代码变更内容完全相关。

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/codex-priority-billing-source

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.

@github-actions github-actions Bot added size/XL Extra Large PR (> 1000 lines) enhancement New feature or request area:UI area:i18n area:core area:session labels Mar 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new system setting that allows administrators to choose whether Codex Priority billing should be based on the requested or actual service tier. This change ensures flexibility in billing practices and maintains compatibility with existing systems while providing a smooth transition for users.

Highlights

  • Codex Priority Billing Source Setting: Introduces a system setting to determine Codex Priority billing based on either the requested or actual service tier, defaulting to the requested tier.
  • Billing Source Routing: Routes the selected billing source through response handling, special setting audit data, settings UI, and migration wiring to ensure consistent application of the setting.
  • Old-Database Compatibility: Enhances compatibility with older databases by preserving existing settings when the new column is missing and gracefully handling saves/init paths.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new system setting codex_priority_billing_source to control billing for Codex Priority requests. The changes are comprehensive, touching the database schema, API, UI, and core proxy logic. The implementation includes robust backward compatibility for older database schemas by gracefully handling missing columns during reads and writes, which is an excellent addition. The billing logic has been refactored into the ProxySession class, improving code organization and encapsulation. The new feature is also well-covered by new integration and unit tests. Overall, this is a high-quality contribution. I have one minor suggestion to improve logging for consistency.

Comment on lines +823 to +830
const codexPriorityBillingSource =
systemSettings.codexPriorityBillingSource === "actual" ? "actual" : "requested";

if (billingModelSource !== systemSettings.billingModelSource) {
logger.warn(
`[ProxySession] Invalid billingModelSource: ${String(systemSettings.billingModelSource)}, fallback to "redirected"`
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For consistency and better debugging, it's good practice to add a warning log for an invalid codexPriorityBillingSource value, similar to how it's done for billingModelSource. This will help identify if an unexpected value is ever present in the database. I've also made the validation slightly more robust to explicitly check for valid enum values.

          const codexPriorityBillingSource =
            systemSettings.codexPriorityBillingSource === "actual" ||
            systemSettings.codexPriorityBillingSource === "requested"
              ? systemSettings.codexPriorityBillingSource
              : "requested";

          if (billingModelSource !== systemSettings.billingModelSource) {
            logger.warn(
              `[ProxySession] Invalid billingModelSource: ${String(systemSettings.billingModelSource)}, fallback to "redirected"`
            );
          }

          if (codexPriorityBillingSource !== systemSettings.codexPriorityBillingSource) {
            logger.warn(
              `[ProxySession] Invalid codexPriorityBillingSource: ${String(systemSettings.codexPriorityBillingSource)}, fallback to "requested"`
            );
          }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复,见 a00c88e:补了 codexPriorityBillingSource 的显式白名单校验和 fallback warning。

Comment thread drizzle/0085_busy_ken_ellis.sql Outdated
@@ -0,0 +1 @@
ALTER TABLE "system_settings" ADD COLUMN "codex_priority_billing_source" varchar(20) DEFAULT 'requested' NOT NULL; No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Missing trailing newline

The file ends without a trailing newline (\ No newline at end of file in the diff). While Drizzle and most PostgreSQL clients handle this fine at runtime, POSIX convention and many linters expect text files to end with a newline. Consider adding one to keep consistent with the rest of the migration files.

Suggested change
ALTER TABLE "system_settings" ADD COLUMN "codex_priority_billing_source" varchar(20) DEFAULT 'requested' NOT NULL;
ALTER TABLE "system_settings" ADD COLUMN "codex_priority_billing_source" varchar(20) DEFAULT 'requested' NOT NULL;
Prompt To Fix With AI
This is a comment left during a code review.
Path: drizzle/0085_busy_ken_ellis.sql
Line: 1

Comment:
**Missing trailing newline**

The file ends without a trailing newline (`\ No newline at end of file` in the diff). While Drizzle and most PostgreSQL clients handle this fine at runtime, POSIX convention and many linters expect text files to end with a newline. Consider adding one to keep consistent with the rest of the migration files.

```suggestion
ALTER TABLE "system_settings" ADD COLUMN "codex_priority_billing_source" varchar(20) DEFAULT 'requested' NOT NULL;
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复,见 a00c88e:迁移文件已补 trailing newline。

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b70aa558c2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/app/v1/_lib/proxy/session.ts Outdated
Comment on lines +853 to +857
: "redirected",
codexPriorityBillingSource:
hasPersistedCachedSettings && cachedSettings.codexPriorityBillingSource === "actual"
? "actual"
: "requested",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid billing with hardcoded defaults on settings read failure

If getSystemSettings() throws before the in-memory cache has been warmed (for example right after a restart during a transient DB outage), this block silently switches billing to redirected/requested. updateRequestCostFromUsage() and resolveCodexPriorityBillingDecision() now consume these values, so requests from installations configured for billingModelSource="original" or codexPriorityBillingSource="actual" will be charged with the wrong pricing instead of being left unbilled as before.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复,见 a00c88e:无可用缓存时不再按 hardcoded defaults 计费,而是跳过价格解析;同时补了 direct read 失败与 cached fallback 的回归测试。

Comment on lines +62 to +63
billingModelSource: validated.billingModelSource,
codexPriorityBillingSource: validated.codexPriorityBillingSource,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate cached settings after API billing changes

Now that /api/admin/system-config can update billingModelSource and codexPriorityBillingSource, this route also needs to clear the 60s getCachedSystemSettings() cache the same way saveSystemSettings() does. Otherwise API-driven changes can leave proxy code paths that read cached settings using the previous billing behavior for up to a minute.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

已修复,见 a00c88e:/api/admin/system-config 现在会在更新后主动 invalidateSystemSettingsCache。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/repository/system-config.ts (1)

230-257: 三级降级查询策略合理,但建议添加注释说明。

当前实现了三级降级查询:

  1. 完整字段集(含 codexPriorityBillingSource
  2. 移除 codexPriorityBillingSource 的字段集
  3. 最小字段集(仅 7 个核心字段)

这种设计能够兼容多个数据库版本,但最小字段集仅包含少量字段,依赖 toSystemSettings 转换器来填充缺失字段的默认值。建议在最小字段集处添加注释,说明其使用场景和依赖关系。

💡 建议添加注释
          logger.warn("system_settings 表存在多个缺失列,继续使用最小字段集读取。", {
            error: fallbackError,
          });

+          // 最小字段集仅包含核心标识和时间戳字段
+          // 其他字段将由 toSystemSettings 转换器填充默认值
          const minimalSelection = {
            id: systemSettings.id,
            siteTitle: systemSettings.siteTitle,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/repository/system-config.ts` around lines 230 - 257, Add a brief inline
comment above the minimalSelection definition in system-config.ts describing
that this block is the third/final fallback of the three-tier query strategy
(after the full selection and selectionWithoutCodexPriorityBillingSource), that
minimalSelection includes only seven core fields to maintain compatibility with
older DB schemas, and that missing fields are expected to be populated later by
the toSystemSettings converter; reference symbols: minimalSelection,
selectionWithoutCodexPriorityBillingSource, toSystemSettings,
isUndefinedColumnError, systemSettings, and db so maintainers can find the
fallback logic and understand its dependency on default-filling in the
conversion layer.
tests/integration/billing-model-source.test.ts (1)

456-521: 把 Session / 限流两条计费路径也一起断言

这些新增 Codex 场景里,有些用例只校验了 updateMessageRequestCost,有些最多再校验 SessionManager.updateSessionUsage,但没有把 RateLimitService.trackCost 一起守住。当前改动同一份计费决策会同时驱动 DB、Session 和限流三条路径;如果其中一条还在按旧的 requested/actual 逻辑走,这组测试仍然可能全部通过。建议像前面的 runScenario 一样,把三处结果收口成统一断言。

Also applies to: 523-575, 645-700, 702-757, 759-814, 816-868, 870-928

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/billing-model-source.test.ts` around lines 456 - 521, The
test only asserts DB and session billing paths but omits the rate-limit billing
path; add a mock implementation for RateLimitService.trackUserDailyCost (or
reuse the existing mock) to capture the tracked cost (like you do for
updateMessageRequestCost and SessionManager.updateSessionUsage), then after
calling ProxyResponseHandler.dispatch and drainAsyncTasks assert the captured
rate-limit cost equals the same expected value (e.g. "32"); update the current
test and the other listed test blocks to include this third assertion and
reference the mocked RateLimitService.trackUserDailyCost alongside
updateMessageRequestCost and SessionManager.updateSessionUsage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/v1/_lib/proxy/session.ts`:
- Around line 811-858: The current loadBillingSettings flow calls
getSystemSettings and on error falls back to getCachedSystemSettings, but
getCachedSystemSettings can itself call getSystemSettings on cache miss, causing
a second DB read; change loadBillingSettings to avoid any potential second DB
call after the initial failure by using a cache-only getter or an option that
disables refresh. Update loadBillingSettings (and billingSettingsPromise logic)
to, on the first catch from getSystemSettings, import and call a cache-only API
(e.g., a new getCachedSystemSettingsNoRefresh / getCachedSystemSettingsOnlyCache
exported from the system-settings-cache module) and if that returns no persisted
cache (hasPersistedCachedSettings false) immediately return the safe defaults
(billingModelSource: "redirected", codexPriorityBillingSource: "requested")
without attempting any further system DB reads. Ensure references to
getSystemSettings and getCachedSystemSettings in the code are updated to use the
new cache-only call so a DB retry is not triggered on the hot proxy path.

In `@src/repository/_shared/transformers.ts`:
- Line 200: Normalize codexPriorityBillingSource by whitelisting allowed values:
when building the settings object (the place using
dbSettings?.codexPriorityBillingSource, e.g., the transform/mapper that sets
codexPriorityBillingSource), only accept "requested" or "actual" and otherwise
fallback to "requested"; implement this by checking the dbSettings value (e.g.,
const v = dbSettings?.codexPriorityBillingSource) and setting
codexPriorityBillingSource = v === "actual" ? "actual" : "requested" so
arbitrary strings are not passed through.

---

Nitpick comments:
In `@src/repository/system-config.ts`:
- Around line 230-257: Add a brief inline comment above the minimalSelection
definition in system-config.ts describing that this block is the third/final
fallback of the three-tier query strategy (after the full selection and
selectionWithoutCodexPriorityBillingSource), that minimalSelection includes only
seven core fields to maintain compatibility with older DB schemas, and that
missing fields are expected to be populated later by the toSystemSettings
converter; reference symbols: minimalSelection,
selectionWithoutCodexPriorityBillingSource, toSystemSettings,
isUndefinedColumnError, systemSettings, and db so maintainers can find the
fallback logic and understand its dependency on default-filling in the
conversion layer.

In `@tests/integration/billing-model-source.test.ts`:
- Around line 456-521: The test only asserts DB and session billing paths but
omits the rate-limit billing path; add a mock implementation for
RateLimitService.trackUserDailyCost (or reuse the existing mock) to capture the
tracked cost (like you do for updateMessageRequestCost and
SessionManager.updateSessionUsage), then after calling
ProxyResponseHandler.dispatch and drainAsyncTasks assert the captured rate-limit
cost equals the same expected value (e.g. "32"); update the current test and the
other listed test blocks to include this third assertion and reference the
mocked RateLimitService.trackUserDailyCost alongside updateMessageRequestCost
and SessionManager.updateSessionUsage.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 467d634e-f865-4b0f-a4c1-d26bd960cf48

📥 Commits

Reviewing files that changed from the base of the PR and between 650dbc6 and b70aa55.

📒 Files selected for processing (29)
  • drizzle/0085_busy_ken_ellis.sql
  • drizzle/meta/0085_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/settings/config.json
  • messages/ja/settings/config.json
  • messages/ru/settings/config.json
  • messages/zh-CN/settings/config.json
  • messages/zh-TW/settings/config.json
  • src/actions/system-config.ts
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/api/admin/system-config/route.ts
  • src/app/v1/_lib/proxy/response-handler.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/drizzle/schema.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/utils/special-settings.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.test.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • src/types/special-settings.ts
  • src/types/system-config.ts
  • tests/integration/billing-model-source.test.ts
  • tests/unit/actions/system-config-save.test.ts
  • tests/unit/lib/config/system-settings-cache.test.ts
  • tests/unit/lib/utils/special-settings.test.ts
  • tests/unit/proxy/session.test.ts
  • tests/unit/repository/system-config-update-missing-columns.test.ts

Comment thread src/app/v1/_lib/proxy/session.ts
Comment thread src/repository/_shared/transformers.ts Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

Reviewed the diff for billing-source selection semantics, settings persistence/migration compatibility, admin UI wiring, and the added tests.
No reportable issues met the >=80 confidence threshold after validation.

PR Size: XL

  • Lines changed: 5124
  • Files changed: 29
  • Split suggestions:
    • PR 1: DB migration + src/drizzle/schema.ts + repository compatibility
    • PR 2: Proxy billing decision logic (src/app/v1/_lib/proxy/*)
    • PR 3: Settings UI + i18n message updates (src/app/[locale]/settings/*, messages/*)
    • PR 4: Tests only (tests/*)

Review Coverage

  • Logic and correctness - No issues found
  • Security (OWASP Top 10) - No issues found
  • Error handling - No issues found
  • Type safety - No issues found
  • Documentation accuracy - No issues found
  • Test coverage - No missing critical tests identified
  • Code clarity - No issues found

Automated review by Codex AI

Comment thread drizzle/0085_busy_ken_ellis.sql Outdated
@@ -0,0 +1 @@
ALTER TABLE "system_settings" ADD COLUMN "codex_priority_billing_source" varchar(20) DEFAULT 'requested' NOT NULL; No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Low] [STANDARD-VIOLATION] SQL migration file missing trailing newline

Why this is a problem: The file ends without a trailing newline character. While PostgreSQL and most clients handle this correctly at runtime, POSIX convention and the project's other migration files follow the convention of ending text files with a newline. The Drizzle tooling also expects this format.

Suggested fix:

ALTER TABLE "system_settings" ADD COLUMN "codex_priority_billing_source" varchar(20) DEFAULT 'requested' NOT NULL;

(Note the trailing newline after the semicolon)

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

This PR adds a codexPriorityBillingSource system setting to control whether Codex Priority billing uses the requested or actual service tier. The implementation is solid with proper error handling, backward compatibility for databases missing the new column, and comprehensive test coverage.

PR Size: XL

  • Lines changed: 5,124 (4,962 additions + 162 deletions)
  • Files changed: 29

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 1

Low Priority Issues (Author's Discretion)

  1. SQL migration file missing trailing newline (drizzle/0085_busy_ken_ellis.sql)
    • The file ends without a trailing newline character, deviating from POSIX convention and the pattern of other migration files in the project.
    • No runtime impact; cosmetic only.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Well implemented with graceful fallbacks
  • Type safety - Proper TypeScript types and Zod validation
  • Documentation accuracy - Types have appropriate comments
  • Test coverage - Comprehensive tests for both modes and edge cases
  • Code clarity - Good

Implementation Highlights

  • Backward compatibility handled via graceful column-missing detection (42703 error code)
  • Proper caching of billing settings per request with Promise-based concurrency safety
  • Correct fallback behavior when actual mode is set but response omits service_tier
  • hasPriorityServiceTierSpecialSetting correctly handles legacy audit records

Automated review by Claude AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • Identified the current open PR as #960.
  • Computed PR size as XL (5124 lines changed across 29 files) and applied label size/XL.
  • Completed a diff-scoped review across the 6 required perspectives; no validated findings met the >=80 confidence reporting threshold.
  • Posted the required summary review via gh pr review (no inline comments were posted).

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Comment on lines +212 to 219
if (
codexServiceTierResult.billingSourcePreference == null &&
codexServiceTierResult.resolvedFrom == null &&
codexServiceTierResult.actualServiceTier != null
) {
return codexServiceTierResult.actualServiceTier === "priority";
}
return codexServiceTierResult.effectivePriority;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Redundant backward-compat branch in hasPriorityServiceTierSpecialSetting

The backward-compatibility guard (lines 212–218) is logically redundant. For old records (before this PR), effectivePriority was always computed as actualServiceTier === "priority", so falling through to return codexServiceTierResult.effectivePriority on line 219 would produce the exact same result. The extra branch adds complexity without changing behavior. Consider simplifying to just return effectivePriority unconditionally:

Suggested change
if (
codexServiceTierResult.billingSourcePreference == null &&
codexServiceTierResult.resolvedFrom == null &&
codexServiceTierResult.actualServiceTier != null
) {
return codexServiceTierResult.actualServiceTier === "priority";
}
return codexServiceTierResult.effectivePriority;
return codexServiceTierResult.effectivePriority;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/utils/special-settings.ts
Line: 212-219

Comment:
**Redundant backward-compat branch in `hasPriorityServiceTierSpecialSetting`**

The backward-compatibility guard (lines 212–218) is logically redundant. For old records (before this PR), `effectivePriority` was always computed as `actualServiceTier === "priority"`, so falling through to `return codexServiceTierResult.effectivePriority` on line 219 would produce the exact same result. The extra branch adds complexity without changing behavior. Consider simplifying to just return `effectivePriority` unconditionally:

```suggestion
    return codexServiceTierResult.effectivePriority;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +234 to +243
try {
billingSourcePreference = await session.getCodexPriorityBillingSource();
} catch (error) {
logger.warn(
"[ResponseHandler] Failed to load codex priority billing source, fallback to requested",
{
error: error instanceof Error ? error.message : String(error),
}
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Dead catch block around getCodexPriorityBillingSource()

session.getCodexPriorityBillingSource() never throws — loadBillingSettings() catches all errors internally and returns a default value. The try/catch wrapping it is dead code, since exceptions are swallowed inside the method. Consider removing the try/catch to reduce noise, or adding a comment explaining why it's kept for future safety:

Suggested change
try {
billingSourcePreference = await session.getCodexPriorityBillingSource();
} catch (error) {
logger.warn(
"[ResponseHandler] Failed to load codex priority billing source, fallback to requested",
{
error: error instanceof Error ? error.message : String(error),
}
);
}
billingSourcePreference = await session.getCodexPriorityBillingSource();
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/v1/_lib/proxy/response-handler.ts
Line: 234-243

Comment:
**Dead catch block around `getCodexPriorityBillingSource()`**

`session.getCodexPriorityBillingSource()` never throws — `loadBillingSettings()` catches all errors internally and returns a default value. The `try/catch` wrapping it is dead code, since exceptions are swallowed inside the method. Consider removing the try/catch to reduce noise, or adding a comment explaining why it's kept for future safety:

```suggestion
  billingSourcePreference = await session.getCodexPriorityBillingSource();
```

How can I resolve this? If you propose a fix, please make it concise.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/app/api/admin/system-config/route.ts (1)

59-69: ⚠️ Potential issue | 🟡 Minor

更新 JSDoc 注释以准确反映 API 支持的字段

POST 端点的 JSDoc 注释(第 35-43 行)列出的字段不完整。注释仅列出 7 个字段,但实现实际上处理 9 个字段,缺少 billingModelSourcecodexPriorityBillingSource 的说明。请更新注释以匹配实现:

 * Body: {
 *   siteTitle: string;
 *   allowGlobalUsageView: boolean;
 *   currencyDisplay?: string;
 *   billingModelSource?: string;
 *   codexPriorityBillingSource?: string;
 *   enableAutoCleanup?: boolean;
 *   cleanupRetentionDays?: number;
 *   cleanupSchedule?: string;
 *   cleanupBatchSize?: number;
 * }

另外,如果 API 为设计而故意限制 UpdateSystemSettingsSchema 中的某些字段(如 timezoneenableClientVersionCheck 等),建议在注释中说明原因,以避免将来的混淆。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/api/admin/system-config/route.ts` around lines 59 - 69, Update the
JSDoc for the POST endpoint in route.ts so it accurately documents all fields
accepted by updateSystemSettings (referenced function updateSystemSettings and
the body shape used when calling it), adding billingModelSource and
codexPriorityBillingSource and the optional markers for currencyDisplay,
enableAutoCleanup, cleanupRetentionDays, cleanupSchedule, and cleanupBatchSize
to match the implementation; also add a short note in the comment explaining any
intentionally omitted schema fields from UpdateSystemSettingsSchema (e.g.,
timezone or enableClientVersionCheck) so readers know those exclusions are by
design.
🧹 Nitpick comments (1)
src/app/v1/_lib/proxy/session.ts (1)

873-880: 建议添加代码注释说明 billingModelSource 不同场景下的默认值差异

当设置完全不可用时,此处默认为 "redirected",而 src/repository/system-config.ts 中的 createFallbackSettings() 默认为 "original"。这个差异是有意设计的:

  • "original":用于初始化和表不存在的场景
  • "redirected":用于运行时请求处理中所有读取失败的场景(更保守的计费策略)

然而代码缺少注释解释这个意图,容易引起误解。建议在两处都添加注释说明这个不同,帮助维护者理解这不是无意的不一致。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/v1/_lib/proxy/session.ts` around lines 873 - 880, 在 ProxySession
的错误分支(即设置读取失败时设置 billingModelSource 为 "redirected" 的逻辑)和 createFallbackSettings
函数中添加注释,明确说明两处默认值不同是有意为之:createFallbackSettings 使用 "original" 用于初始化或表缺失场景,而
ProxySession 的 "redirected"
是运行时读取失败时采用的更保守计费策略;在注释中引用相关符号(billingModelSource、codexPriorityBillingSource、ProxySession
和 createFallbackSettings)并简短说明每个默认值适用的场景和理由,以避免误解。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/app/api/admin/system-config/route.ts`:
- Around line 59-69: Update the JSDoc for the POST endpoint in route.ts so it
accurately documents all fields accepted by updateSystemSettings (referenced
function updateSystemSettings and the body shape used when calling it), adding
billingModelSource and codexPriorityBillingSource and the optional markers for
currencyDisplay, enableAutoCleanup, cleanupRetentionDays, cleanupSchedule, and
cleanupBatchSize to match the implementation; also add a short note in the
comment explaining any intentionally omitted schema fields from
UpdateSystemSettingsSchema (e.g., timezone or enableClientVersionCheck) so
readers know those exclusions are by design.

---

Nitpick comments:
In `@src/app/v1/_lib/proxy/session.ts`:
- Around line 873-880: 在 ProxySession 的错误分支(即设置读取失败时设置 billingModelSource 为
"redirected" 的逻辑)和 createFallbackSettings
函数中添加注释,明确说明两处默认值不同是有意为之:createFallbackSettings 使用 "original" 用于初始化或表缺失场景,而
ProxySession 的 "redirected"
是运行时读取失败时采用的更保守计费策略;在注释中引用相关符号(billingModelSource、codexPriorityBillingSource、ProxySession
和 createFallbackSettings)并简短说明每个默认值适用的场景和理由,以避免误解。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67d29fea-9242-443a-8be2-d3d18788ef98

📥 Commits

Reviewing files that changed from the base of the PR and between b70aa55 and a00c88e.

📒 Files selected for processing (9)
  • drizzle/0085_busy_ken_ellis.sql
  • src/app/api/admin/system-config/route.ts
  • src/app/v1/_lib/proxy/session.ts
  • src/lib/config/index.ts
  • src/lib/config/system-settings-cache.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • tests/integration/billing-model-source.test.ts
  • tests/unit/proxy/session.test.ts
✅ Files skipped from review due to trivial changes (1)
  • drizzle/0085_busy_ken_ellis.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/lib/config/system-settings-cache.ts
  • tests/unit/proxy/session.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a00c88e146

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +262 to +266
requestedServiceTier,
actualServiceTier,
billingSourcePreference,
resolvedFrom,
effectivePriority: effectiveTier === "priority",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not record Fast mode when billing settings fell back

When loadBillingSettings() falls back to source: "default" (for example right after a restart while the settings query is failing), getResolvedPricingByBillingSource() now skips pricing entirely in session.ts:745-747, but resolveCodexPriorityBillingDecision() still returns a concrete billingSourcePreference/effectivePriority here. That causes codex_service_tier_result to be written with effectivePriority: true for requests like service_tier: "priority", and the log detail UIs then show a Fast badge even though the request was not billed at all. This makes outage-time billing/audit data misleading.

Useful? React with 👍 / 👎.

@ding113
ding113 merged commit 040b9d0 into dev Mar 21, 2026
29 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Mar 21, 2026
@github-actions github-actions Bot mentioned this pull request Mar 22, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:i18n area:session area:UI enhancement New feature or request size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant