Skip to content

feat: add periodic quota policies for API keys#5791

Open
zhukangfeng wants to merge 2 commits into
QuantumNous:mainfrom
zhukangfeng:codex/token-periodic-quota
Open

feat: add periodic quota policies for API keys#5791
zhukangfeng wants to merge 2 commits into
QuantumNous:mainfrom
zhukangfeng:codex/token-periodic-quota

Conversation

@zhukangfeng

@zhukangfeng zhukangfeng commented Jun 28, 2026

Copy link
Copy Markdown

⚠️ 提交说明 / PR Notice

Important

  • This PR description is intentionally summarized and organized for review. The implementation was AI-assisted and locally verified before submission.

📝 变更描述 / Description

This PR adds optional periodic quota policies for API keys. A token can now have an additional budget window on top of the existing user quota and token total quota, so administrators can limit usage by 5-hour, daily, weekly, monthly, or custom-minute cycles.

Backend changes:

  • Add TokenQuotaPolicy persistence and migration with period mode, anchor time, quota, used quota, next reset time, boundary mode, exhausted action, and auto-resume metadata.
  • Integrate periodic quota checks into token pre-consume/post-consume billing paths.
  • Support graceful boundary behavior, where the current in-flight request is allowed and the next request is blocked after settlement.
  • Support strict pre-check behavior, where requests that would exceed the current periodic quota are rejected before upstream dispatch.
  • Support reject-only and temporary token-disable exhaustion actions.
  • Add lazy request-path reset and a scheduled system task for automatic recovery.
  • Add manual reset/unblock API for clearing current periodic usage and restoring policy-disabled tokens.
  • Preserve existing token quota accounting and keep normal tokens fast by short-circuiting when periodic quota is not enabled; enabled tokens reuse a per-request policy snapshot.
  • Return explicit token_quota_policy_exhausted errors with approximate recovery time wording and no used/quota amounts in client-facing messages.
  • Add common operation logs for policy exhaustion, automatic recovery, manual reset, and policy update disable/restore.
  • Synchronize backend OpenAPI documentation for token policy fields and the reset endpoint.

Frontend changes:

  • Add periodic quota controls to the API key drawer.
  • Show temporary periodic-quota disabled status in the key table with tooltip details and approximate recovery time.
  • Add manual reset/unblock action with confirmation.
  • Render new common usage-log operation details and token associations.
  • Add translations for all default frontend locales: en, zh, fr, ru, ja, vi.

Testing and review coverage:

  • Add model tests for period windows, custom minimums, monthly day clamp, validation, atomic consume/refund/reset, recovery, and update semantics.
  • Add service tests for strict/graceful boundary behavior, billing settlement, refund behavior, explicit quota errors, and request-path blocking.
  • Add controller tests and an end-to-end mocked-upstream test that verifies rejection occurs before upstream dispatch.
  • Include UI screenshots under docs/pr-screenshots/token-periodic-quota/.

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (none)

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

Local verification:

go test ./...
cd web/default && bun run typecheck
cd web/default && bun run build
git diff --check

Manual/UI proof files included in this PR:

  • docs/pr-screenshots/token-periodic-quota/01-api-keys-page.png
  • docs/pr-screenshots/token-periodic-quota/02-api-key-periodic-quota-drawer.png

Additional notes:

  • The current git user is not one of the recurring historical core commit authors found in local git log; this contribution was AI-assisted and reviewed locally before PR creation.

Summary by CodeRabbit

  • New Features
    • Added periodic quota-policy support for tokens and API keys, including quota reset/restore, recovery states, and quota-aware status/badges.
    • Introduced a new API endpoint to reset token quota policy and updated OpenAPI/DB schema accordingly.
    • UI now includes periodic quota configuration controls; usage logs render localized system quota messages.
  • Bug Fixes
    • Improved enforcement/settlement so quota policy exhaustion yields a clearer, user-facing 429 and prevents incorrect quota/billing flows.
  • Tests
    • Added/expanded token quota policy unit, API, migration compatibility, and end-to-end coverage.

Add optional API key periodic quota policies with preset/custom windows, anchor time, strict or graceful boundary handling, reject-only or temporary-disable actions, auto recovery, and manual reset support.

Integrate policy accounting into token pre-consume/post-consume billing paths while preserving existing user/token quota behavior, returning explicit token_quota_policy_exhausted errors for periodic quota exhaustion, and caching policy snapshots per relay request to avoid extra reads for normal tokens.

Expose policy configuration and temporary-disabled status in the default UI with localized labels/tooltips/log rendering across supported locales, add common usage log descriptors, and synchronize the backend OpenAPI token schema and reset route.

Add model/service/controller/E2E tests, PR screenshots, and verify with go test ./..., web/default bun run typecheck, web/default bun run build, and git diff --check.
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 97c38420-6ef2-4810-aa84-2ff0606cbafb

📥 Commits

Reviewing files that changed from the base of the PR and between f28142f and abcf6d5.

⛔ Files ignored due to path filters (2)
  • docs/pr-screenshots/token-periodic-quota/01-api-keys-page.png is excluded by !**/*.png
  • docs/pr-screenshots/token-periodic-quota/02-api-key-periodic-quota-drawer.png is excluded by !**/*.png
📒 Files selected for processing (47)
  • constant/context_key.go
  • controller/token.go
  • controller/token_periodic_quota_e2e_test.go
  • controller/token_test.go
  • docs/openapi/api.json
  • i18n/keys.go
  • i18n/locales/en.yaml
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • middleware/auth.go
  • model/log.go
  • model/main.go
  • model/system_task.go
  • model/task_cas_test.go
  • model/token.go
  • model/token_quota_policy.go
  • model/token_quota_policy_test.go
  • relay/common/relay_info.go
  • router/api-router.go
  • service/billing_session.go
  • service/pre_consume_quota.go
  • service/quota.go
  • service/task_billing_test.go
  • service/token_quota_policy.go
  • service/token_quota_policy_test.go
  • types/error.go
  • web/default/src/features/keys/api.ts
  • web/default/src/features/keys/components/api-keys-cells.tsx
  • web/default/src/features/keys/components/api-keys-columns.tsx
  • web/default/src/features/keys/components/api-keys-mutate-drawer.tsx
  • web/default/src/features/keys/components/api-keys-table.tsx
  • web/default/src/features/keys/components/data-table-row-actions.tsx
  • web/default/src/features/keys/constants.ts
  • web/default/src/features/keys/lib/api-key-form.ts
  • web/default/src/features/keys/lib/quota-policy-status.ts
  • web/default/src/features/keys/types.ts
  • web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
  • web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
  • web/default/src/features/usage-logs/lib/format.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/static-keys.ts
  • web/default/src/lib/format.ts
✅ Files skipped from review due to trivial changes (7)
  • model/task_cas_test.go
  • i18n/locales/en.yaml
  • i18n/locales/zh-TW.yaml
  • i18n/locales/zh-CN.yaml
  • web/default/src/i18n/static-keys.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/vi.json
🚧 Files skipped from review as they are similar to previous changes (40)
  • service/task_billing_test.go
  • types/error.go
  • model/system_task.go
  • router/api-router.go
  • web/default/src/features/keys/constants.ts
  • web/default/src/features/usage-logs/lib/format.ts
  • web/default/src/features/keys/api.ts
  • constant/context_key.go
  • web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
  • i18n/keys.go
  • model/main.go
  • middleware/auth.go
  • web/default/src/features/keys/lib/quota-policy-status.ts
  • model/log.go
  • web/default/src/features/keys/types.ts
  • web/default/src/features/keys/components/api-keys-columns.tsx
  • service/pre_consume_quota.go
  • controller/token.go
  • web/default/src/features/keys/components/api-keys-table.tsx
  • controller/token_periodic_quota_e2e_test.go
  • web/default/src/features/keys/components/api-keys-mutate-drawer.tsx
  • model/token.go
  • web/default/src/features/keys/components/api-keys-cells.tsx
  • service/token_quota_policy_test.go
  • web/default/src/lib/format.ts
  • relay/common/relay_info.go
  • docs/openapi/api.json
  • web/default/src/features/keys/components/data-table-row-actions.tsx
  • web/default/src/i18n/locales/zh.json
  • model/token_quota_policy_test.go
  • service/quota.go
  • web/default/src/i18n/locales/ru.json
  • controller/token_test.go
  • web/default/src/features/keys/lib/api-key-form.ts
  • service/billing_session.go
  • service/token_quota_policy.go
  • web/default/src/i18n/locales/ja.json
  • web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
  • web/default/src/i18n/locales/fr.json
  • model/token_quota_policy.go

Walkthrough

Adds token quota policy storage, enforcement, reset, API, frontend, localization, and tests across the stack.

Changes

Token periodic quota policy

Layer / File(s) Summary
Model and persistence
model/token_quota_policy.go, model/token.go, model/log.go, model/main.go, model/system_task.go, constant/context_key.go, types/error.go
Defines quota policy types and validation, stores quota policy state on tokens, adds DB operations for consume, settle, refund, reset, exhaustion, attach, and save flows, registers the model in migrations, and adds token-scoped log helpers and system-task identifiers.
Token auth and relay integration
relay/common/relay_info.go, service/token_quota_policy.go, service/quota.go, service/billing_session.go, service/pre_consume_quota.go, middleware/auth.go
Extends request context and relay info with token quota policy state, updates token validation and token lookup behavior, maps quota-policy exhaustion to auth errors, and threads the enabled flag into request-time quota decisions.
Token API, router, OpenAPI, backend messages
controller/token.go, router/api-router.go, docs/openapi/api.json, i18n/keys.go, i18n/locales/*.yaml
Attaches quota policy data to token API responses, validates and persists quota policy on create and update, adds manual reset handling and route wiring, extends OpenAPI with the reset endpoint and policy schema, and adds backend i18n keys and translations for quota-policy exhaustion states.
Frontend quota policy model and controls
web/default/src/features/keys/*, web/default/src/lib/format.ts
Adds frontend quota-policy schema and form mapping, the API helper for resetting a token quota policy, quota-disable state detection, status badges, table status rendering, drawer controls, and row actions for manual reset.
Usage log rendering and frontend locale strings
web/default/src/features/usage-logs/*, web/default/src/i18n/locales/*.json, web/default/src/i18n/static-keys.ts
Adds system-log rendering helpers, uses localized system content in usage-log details, and extends frontend static keys and locale files with periodic quota configuration, recovery, exhaustion, boundary handling, and reset/restore strings.
Tests and migration coverage
model/token_quota_policy_test.go, model/task_cas_test.go, service/token_quota_policy_test.go, service/task_billing_test.go, controller/token_test.go, controller/token_periodic_quota_e2e_test.go
Covers quota-policy model behavior, service enforcement, controller endpoints, migration compatibility, E2E request flow, and test database setup and cleanup for the new quota policy table.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • QuantumNous/new-api#1775: Both PRs touch service/pre_consume_quota.go, where the current changes add quota-policy-aware trust and pre-consume handling.
  • QuantumNous/new-api#2877: Both PRs modify service/billing_session.go settlement and refund control flow for token quota accounting.
  • QuantumNous/new-api#5680: The periodic reset system task builds on the system-task scheduling path used by the token quota reset handler.

Suggested labels

enhancement

Suggested reviewers

  • creamlike1024

Poem

🐇 Hop, hop — the quota clock now sings,
Fresh windows, resets, and little rabbit things.
When tokens run dry, the 429 bell chimes,
Then the bunny resets and counts all the times.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.22% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely matches the main change: adding periodic quota policies for API keys.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
service/billing_session.go

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.20][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

service/quota.go

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.36][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

service/pre_consume_quota.go

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[01.16][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

  • 25 others

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.

@zhukangfeng zhukangfeng marked this pull request as ready for review June 28, 2026 13:15

@coderabbitai coderabbitai 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.

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (2)
controller/token.go (1)

264-273: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make token and quota-policy writes atomic.

AddToken inserts cleanToken before saving the policy, and UpdateToken updates the base token before saving the policy. If SaveTokenQuotaPolicyForToken fails, the API returns an error after partially committing token changes. Wrap each base-token write and policy save in one database transaction or model-layer helper that rolls back on policy failure.

Also applies to: 354-369

🤖 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 `@controller/token.go` around lines 264 - 273, `AddToken` and `UpdateToken` are
committing the base token write before `SaveTokenQuotaPolicyForToken`, so a
policy failure can leave partial token state; make the token write and
quota-policy save atomic by using one transaction or a model-layer helper that
rolls back on failure. Update the logic in `AddToken` and `UpdateToken` (and the
shared token/quota-policy write path they call) so `cleanToken.Insert`/the token
update and `model.SaveTokenQuotaPolicyForToken` succeed or fail together, with
errors propagated only after the transaction is aborted.
service/billing_session.go (1)

50-62: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Block refunds after a delta-zero settle reaches policy finalization.

When delta == 0, fundingSettled stays false. If settleTokenQuotaPolicy fails here, Refund() can still refund tokenConsumed/funding via needsRefundLocked, even though Settle is finalizing a completed request. Mark the funding side as committed before returning this settlement error.

Suggested guard
 		tokenErr = settleTokenQuotaPolicy(s.relayInfo, actualQuota)
 		if tokenErr != nil {
+			s.fundingSettled = true
 			return tokenErr
 		}
🤖 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 `@service/billing_session.go` around lines 50 - 62, `Refund()` can still issue
a refund after a zero-delta settlement fails because `fundingSettled` remains
false when `s.funding.Settle(delta)` is skipped. Update the settlement flow in
`BillingSession` so that once `settleTokenQuotaPolicy(s.relayInfo, actualQuota)`
fails after a completed request, the funding side is marked committed before
returning the error, preventing `needsRefundLocked` from treating it as
refundable; use the existing `fundingSettled` flag and the
`Settle`/`settleTokenQuotaPolicy` path to place this guard.
🧹 Nitpick comments (1)
web/default/src/features/keys/components/api-keys-cells.tsx (1)

45-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Match the local prop-typing convention here.

Use an explicit props interface/type alias and access props.apiKey / props.className directly instead of inline destructuring so this new component stays consistent with the web/default component pattern.

As per coding guidelines, "Do not destructure objects unless necessary, especially component props; prefer direct property access such as props.xxx" and "Use function components and Hooks, and give component props an explicit interface or type alias."

🤖 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 `@web/default/src/features/keys/components/api-keys-cells.tsx` around lines 45
- 51, The ApiKeyStatusBadge component is using inline prop destructuring instead
of the local web/default props convention. Introduce an explicit props interface
or type alias for ApiKeyStatusBadge, accept a single props argument, and update
the component to read props.apiKey and props.className directly so it matches
the existing component pattern and coding guidelines.

Source: Coding guidelines

🤖 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 `@middleware/auth.go`:
- Around line 373-378: The quota exhaustion branch in auth.go uses
MsgTokenQuotaPolicyExhaustedPending for all non-auto-resume cases, which reuses
the wrong locale copy for manual-reset policies. Update the message selection
logic around the quotaPolicyErr.AutoResume check to choose a
manual-reset-specific i18n key when AutoResume is false, and keep
MsgTokenQuotaPolicyExhausted only for auto-resume exhaustion with a future
NextResetAt. Make the change in the quota handling flow that sets messageKey and
resetTime so the returned text matches the manual-resume state.

In `@model/token_quota_policy.go`:
- Around line 223-240: The quota exhaustion check in TokenQuotaPolicy update
flow is using a strict greater-than comparison, which leaves exact quota usage
unmarked as exhausted. Update the logic in the token quota update path and the
final return check in the TokenQuotaPolicy methods so they consistently treat
used_quota >= quota as exhausted, matching the rest of the model and ensuring
disable_token is triggered immediately when quota is exactly reached.
- Around line 438-444: The TokenQuotaPolicy exhaustion update is overwriting
exhausted_token_status on repeated calls, which loses the originally enabled
status. Update the logic in the exhaustion path that uses token.Status so it
only sets exhausted_token_status the first time a token is marked exhausted, and
preserves the existing value on subsequent calls; use the TokenQuotaPolicy
update block as the place to guard this before calling Updates.

In `@service/quota.go`:
- Around line 439-448: The quota finalization in the non-playground branch can
fail after wallet/subscription funding has already been updated, leaving
accounting inconsistent. In the `settleTokenQuotaPolicy` and
`postConsumeTokenQuotaPolicy` path inside `quota.go`, either move policy
settlement before the funding mutation or add a compensating rollback that
restores the funding delta if either call returns an error. Keep the fix
localized around the settlement flow so the state changes in `relayInfo`/quota
remain atomic.

In `@service/token_quota_policy.go`:
- Around line 124-141: The strict pre-check path in token quota handling returns
TokenQuotaPolicyExhausted without honoring the disable-token action. Update the
logic in the token quota policy flow around tokenQuotaPolicySnapshotExhausted
and MarkTokenQuotaPolicyExhausted so that when ExhaustedAction is
TokenQuotaExhaustDisableToken, the token is marked disabled before returning the
exhaustion error, matching the non-strict branch behavior and the existing
model.ConsumeTokenQuotaPolicy handling.

In `@web/default/src/features/keys/components/api-keys-columns.tsx`:
- Around line 109-114: The Status column filtering in api-keys-columns.tsx is
using the raw status field, which no longer matches what ApiKeyStatusBadge
displays for quota-derived temporary states. Update the Status column’s filterFn
to filter by the same derived display state used by ApiKeyStatusBadge for each
row, so temporarily quota-disabled keys are excluded from the “Enabled” filter
and can be matched by their visible label.

In `@web/default/src/features/keys/lib/api-key-form.ts`:
- Line 126: The default for quota_policy_anchor_time is being captured at module
load, so the API key form can reuse a stale timestamp on later opens. Move the
Date creation out of API_KEY_FORM_DEFAULT_VALUES and compute it lazily inside
getApiKeyFormDefaultValues so each form open gets a fresh value; keep
transformFormDataToPayload unchanged since it will continue forwarding any
non-nil field.

In `@web/default/src/features/keys/lib/quota-policy-status.ts`:
- Around line 27-35: The quota status helper is treating every exhausted
periodic quota as a temporary disable state, which breaks reject-only exhaustion
handling. Update the logic in the quota status helper to distinguish real
temporary-disabled policies from reject-only exhaustion by checking the policy’s
exhaustion action or persisted temporary-disabled flag before returning an
active/recovering disable state. Use the existing helper/logic around the
quota-policy status computation to gate the “Temporarily disabled” result only
when the policy was actually paused.

In `@web/default/src/features/usage-logs/lib/format.ts`:
- Around line 423-428: The sentinel handling in formatSystemLogTimestamp should
treat numeric 0 and negative values the same way as formatTimestampToDate so
they render as “-” instead of raw numbers. Update the guard in
formatSystemLogTimestamp to return “-” for numeric timestamps that are less than
or equal to 0, while keeping the String(value) fallback only for non-numeric
input; use formatSystemLogTimestamp and formatTimestampToDate as the key
reference points.

---

Outside diff comments:
In `@controller/token.go`:
- Around line 264-273: `AddToken` and `UpdateToken` are committing the base
token write before `SaveTokenQuotaPolicyForToken`, so a policy failure can leave
partial token state; make the token write and quota-policy save atomic by using
one transaction or a model-layer helper that rolls back on failure. Update the
logic in `AddToken` and `UpdateToken` (and the shared token/quota-policy write
path they call) so `cleanToken.Insert`/the token update and
`model.SaveTokenQuotaPolicyForToken` succeed or fail together, with errors
propagated only after the transaction is aborted.

In `@service/billing_session.go`:
- Around line 50-62: `Refund()` can still issue a refund after a zero-delta
settlement fails because `fundingSettled` remains false when
`s.funding.Settle(delta)` is skipped. Update the settlement flow in
`BillingSession` so that once `settleTokenQuotaPolicy(s.relayInfo, actualQuota)`
fails after a completed request, the funding side is marked committed before
returning the error, preventing `needsRefundLocked` from treating it as
refundable; use the existing `fundingSettled` flag and the
`Settle`/`settleTokenQuotaPolicy` path to place this guard.

---

Nitpick comments:
In `@web/default/src/features/keys/components/api-keys-cells.tsx`:
- Around line 45-51: The ApiKeyStatusBadge component is using inline prop
destructuring instead of the local web/default props convention. Introduce an
explicit props interface or type alias for ApiKeyStatusBadge, accept a single
props argument, and update the component to read props.apiKey and
props.className directly so it matches the existing component pattern and coding
guidelines.
🪄 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: c2487986-ae7d-4364-ac85-62329a702501

📥 Commits

Reviewing files that changed from the base of the PR and between 25f9985 and 3def30b.

⛔ Files ignored due to path filters (2)
  • docs/pr-screenshots/token-periodic-quota/01-api-keys-page.png is excluded by !**/*.png
  • docs/pr-screenshots/token-periodic-quota/02-api-key-periodic-quota-drawer.png is excluded by !**/*.png
📒 Files selected for processing (46)
  • constant/context_key.go
  • controller/token.go
  • controller/token_periodic_quota_e2e_test.go
  • controller/token_test.go
  • docs/openapi/api.json
  • i18n/keys.go
  • i18n/locales/en.yaml
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • middleware/auth.go
  • model/log.go
  • model/main.go
  • model/system_task.go
  • model/task_cas_test.go
  • model/token.go
  • model/token_quota_policy.go
  • model/token_quota_policy_test.go
  • relay/common/relay_info.go
  • router/api-router.go
  • service/billing_session.go
  • service/pre_consume_quota.go
  • service/quota.go
  • service/task_billing_test.go
  • service/token_quota_policy.go
  • service/token_quota_policy_test.go
  • types/error.go
  • web/default/src/features/keys/api.ts
  • web/default/src/features/keys/components/api-keys-cells.tsx
  • web/default/src/features/keys/components/api-keys-columns.tsx
  • web/default/src/features/keys/components/api-keys-mutate-drawer.tsx
  • web/default/src/features/keys/components/api-keys-table.tsx
  • web/default/src/features/keys/components/data-table-row-actions.tsx
  • web/default/src/features/keys/lib/api-key-form.ts
  • web/default/src/features/keys/lib/quota-policy-status.ts
  • web/default/src/features/keys/types.ts
  • web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx
  • web/default/src/features/usage-logs/components/dialogs/details-dialog.tsx
  • web/default/src/features/usage-logs/lib/format.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/i18n/static-keys.ts
  • web/default/src/lib/format.ts

Comment thread middleware/auth.go
Comment thread model/token_quota_policy.go Outdated
Comment thread model/token_quota_policy.go
Comment thread service/quota.go
Comment thread service/token_quota_policy.go
Comment thread web/default/src/features/keys/components/api-keys-columns.tsx Outdated
Comment thread web/default/src/features/keys/lib/api-key-form.ts Outdated
Comment thread web/default/src/features/keys/lib/quota-policy-status.ts
Comment thread web/default/src/features/usage-logs/lib/format.ts
Make token and quota policy saves atomic, preserve original token status during repeated exhaustion, and treat exact quota settlement as exhausted.

Rollback funding when post-consume quota policy settlement fails, keep finalized billing sessions from double-refunding, and clarify manual-reset exhaustion errors.

Tighten key status UI filtering, lazy form anchor defaults, reject-only disabled state handling, and log timestamp sentinel formatting.
@zhukangfeng zhukangfeng force-pushed the codex/token-periodic-quota branch from f28142f to abcf6d5 Compare June 28, 2026 14:37

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
service/quota.go (1)

440-461: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The final token-quota write can still leave accounting half-applied.

Lines 441-454 now compensate policy failures, but lines 455-461 still return after funding and policy usage have already been committed. If DecreaseTokenQuota/IncreaseTokenQuota fails there, wallet/subscription state and periodic-policy state stay updated while the token’s own quota does not. This path needs the same atomicity guarantee as the earlier rollback case—either a single transactional helper or compensating rollback for both funding and policy on token-quota write failure.

🤖 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 `@service/quota.go` around lines 440 - 461, The final token-quota update in the
quota flow can still fail after funding and policy changes have already been
applied. In the quota handling path in service/quota.go, update the logic around
settleTokenQuotaPolicy/postConsumeTokenQuotaPolicy and the final
model.DecreaseTokenQuota/model.IncreaseTokenQuota write so the whole sequence is
atomic. If the token-quota write fails, perform compensating rollback for both
the funding update and the policy state, or move the entire operation into a
single transactional helper to keep relayInfo accounting consistent.
🤖 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 `@controller/token.go`:
- Around line 363-370: The update flow in cleanToken.Update() followed by
model.AttachTokenQuotaPolicy(cleanToken) can repopulate cache without the
existing quota policy, causing a race where cached token data may briefly lose
periodic-quota information. Fix the token update path in controller/token.go by
preserving the current quota policy before Update() runs, or by using a
post-write refresh helper that reloads the token from DB and re-caches the fully
populated record. Keep the existing error handling around cleanToken.Update()
and AttachTokenQuotaPolicy(), but ensure the cached token always includes the
prior policy when QuotaPolicy is not being changed.

In `@web/default/src/features/keys/constants.ts`:
- Around line 67-69: The new status option in constants.ts is using a raw
English label, which bypasses translation. Update the status entry in the keys
constants to store an i18n key instead of 'Temporarily disabled', following the
same label/value convention used by the other status options in this feature.
Keep the value unchanged and make sure the label symbol used here matches the
existing i18n key pattern for feature status/option labels so the UI can
translate it properly.

---

Outside diff comments:
In `@service/quota.go`:
- Around line 440-461: The final token-quota update in the quota flow can still
fail after funding and policy changes have already been applied. In the quota
handling path in service/quota.go, update the logic around
settleTokenQuotaPolicy/postConsumeTokenQuotaPolicy and the final
model.DecreaseTokenQuota/model.IncreaseTokenQuota write so the whole sequence is
atomic. If the token-quota write fails, perform compensating rollback for both
the funding update and the policy state, or move the entire operation into a
single transactional helper to keep relayInfo accounting consistent.
🪄 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: 9bbbebfe-05bc-448e-a225-cba968d79f6d

📥 Commits

Reviewing files that changed from the base of the PR and between 3def30b and f28142f.

📒 Files selected for processing (17)
  • controller/token.go
  • i18n/keys.go
  • i18n/locales/en.yaml
  • i18n/locales/zh-CN.yaml
  • i18n/locales/zh-TW.yaml
  • middleware/auth.go
  • model/token_quota_policy.go
  • model/token_quota_policy_test.go
  • service/billing_session.go
  • service/quota.go
  • service/token_quota_policy.go
  • web/default/src/features/keys/components/api-keys-cells.tsx
  • web/default/src/features/keys/components/api-keys-columns.tsx
  • web/default/src/features/keys/constants.ts
  • web/default/src/features/keys/lib/api-key-form.ts
  • web/default/src/features/keys/lib/quota-policy-status.ts
  • web/default/src/features/usage-logs/lib/format.ts
✅ Files skipped from review due to trivial changes (2)
  • i18n/locales/en.yaml
  • i18n/locales/zh-CN.yaml
🚧 Files skipped from review as they are similar to previous changes (11)
  • web/default/src/features/keys/components/api-keys-columns.tsx
  • web/default/src/features/keys/lib/quota-policy-status.ts
  • web/default/src/features/usage-logs/lib/format.ts
  • middleware/auth.go
  • i18n/keys.go
  • web/default/src/features/keys/components/api-keys-cells.tsx
  • service/token_quota_policy.go
  • service/billing_session.go
  • model/token_quota_policy_test.go
  • web/default/src/features/keys/lib/api-key-form.ts
  • model/token_quota_policy.go

Comment thread controller/token.go
Comment on lines +67 to +69
{
label: 'Temporarily disabled',
value: 'quota_policy_disabled',

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Store the new status label as an i18n key, not a raw English literal.

'Temporarily disabled' is user-facing text in a feature constants.ts file, so adding it as a literal here will bypass translation in non-English locales. Use the same i18n-key convention this feature uses for status/option labels instead. As per coding guidelines, frontend UI text must support i18n and feature constants.ts status/option labels must store i18n keys consistently.

🤖 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 `@web/default/src/features/keys/constants.ts` around lines 67 - 69, The new
status option in constants.ts is using a raw English label, which bypasses
translation. Update the status entry in the keys constants to store an i18n key
instead of 'Temporarily disabled', following the same label/value convention
used by the other status options in this feature. Keep the value unchanged and
make sure the label symbol used here matches the existing i18n key pattern for
feature status/option labels so the UI can translate it properly.

Source: Coding guidelines

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant