Skip to content

feat(openai): 支持指定重置卡临期定时使用 - #6290

Open
zenor0 wants to merge 2 commits into
Wei-Shaw:mainfrom
zenor0:feat/openai-reset-credit-production-c
Open

feat(openai): 支持指定重置卡临期定时使用#6290
zenor0 wants to merge 2 commits into
Wei-Shaw:mainfrom
zenor0:feat/openai-reset-credit-production-c

Conversation

@zenor0

@zenor0 zenor0 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

背景与动机

当前 OpenAI 重置卡支持手动立即使用,以及按照 5h/7d 用量阈值自动使用,但还无法针对某一张即将到期的重置卡提前安排使用。

重置卡不用白不用。有效期一长,很容易忘记手动使用,等想起来时,重置机会可能已经过期。本 PR 增加“指定重置卡临期定时使用”能力,让管理员选择具体重置卡,并设置在到期前多少分钟自动执行。

设计概览

整体流程如下:

  1. 刷新 OpenAI 配额,获取完整重置卡列表(credit_id + expires_at)。
  2. 将完整列表缓存至 accounts.extra,用于展示和创建计划。
  3. 管理员选择具体重置卡,并设置提前使用时间。
  4. 保存账号级单卡计划;若执行时间已经到达,则立即唤醒 worker。
  5. worker 在执行窗口消费指定 credit_id
  6. 消费后回读上游状态,并更新账号状态、缓存和审计记录。

使用稳定的 credit ID

重置卡 ID 是普通资源标识,不是 token 或 credential。本 PR 将该 ID 加入管理端配额响应和缓存,直接、唯一地定位目标卡,不再依赖到期时间匹配或自行生成 selector,从而减少额外的匹配、碰撞和失效分支。

{
  "available_count": 2,
  "credits": [
    {
      "id": "credit_xxx",
      "expires_at": "2026-08-28T00:00:00Z"
    }
  ]
}

缓存用于展示和创建计划,不作为执行结果的最终事实来源。旧缓存缺少 ID 时仍可展示,但需要刷新完整快照后才能创建计划。

计划与执行语义

每个 OpenAI OAuth 母账号最多保存一个计划:

{
  "plan_id": "uuid",
  "credit_id": "credit_xxx",
  "expires_at": "2026-08-28T00:00:00Z",
  "lead_time_minutes": 60
}
  • 默认提前 60 分钟,允许范围为 5 到 10080 分钟。
  • 同一张卡可以更新计划;切换到另一张卡前需要先取消原计划。
  • 计划执行时间已经到达时,保存后会立即进入执行流程。
  • Spark 影子账号不支持设置计划。
  • 计划是定向消费授权,不是库存锁定。未进入执行窗口时,现有阈值策略仍可独立运行;进入执行窗口后,定向计划优先执行。

并发与幂等

消费继续使用 account_id + credit_id 生成稳定的 redeem_request_id,保证超时重试和多实例并发不会重复消费同一张卡。

计划更新与 worker 收尾使用 accounts.extra 条件更新(CAS)。worker 只能更新自己读取到的计划,避免旧 worker 在执行结束后清除管理员刚创建的替代计划。

对于 Replayedwindows_reset <= 0,执行流程会重新查询实时库存:

  • 目标卡已经消失:确认消费已完成并继续收尾。
  • 目标卡仍然存在:记录 OPENAI_AUTO_RESET_REPLAY_CONFLICTOPENAI_AUTO_RESET_NO_EFFECT,不得写入成功状态。
  • 库存无法完整确认:保留计划,等待后续重试。

API 与管理端

新增 API:

  • PUT /api/v1/admin/openai/accounts/:id/reset-credit-expiry-target
  • DELETE /api/v1/admin/openai/accounts/:id/reset-credit-expiry-target

设置计划请求:

{
  "credit_id": "credit_xxx",
  "lead_time_minutes": 60
}

配额响应中的 rate_limit_reset_credits.credits[] 新增可选字段 id

管理端支持为每张可用重置卡设置计划、查看预计执行时间和立即执行提示、展示当前计划摘要,以及取消计划。

数据库与持久化

本 PR 没有新增或删除数据库表、字段、索引,也不需要执行 migration。

新增状态复用现有 accounts.extra JSONB 字段:

  • codex_reset_credit_snapshot.credits[].id
  • auto_reset_credit_expiry_target
  • codex_auto_reset_credit_state.trigger_reason = "expiry_target"

repository 增加针对单个 extra key 的原子条件更新能力,用于保护计划替换与 worker 收尾之间的并发一致性。

相关工作

测试

已覆盖:

  • 创建、更新和取消定时计划。
  • 到达执行窗口后立即执行。
  • 指定 credit ID 和稳定 redeem ID 的上游请求。
  • 多实例并发只消费一次。
  • 旧 worker 不会清除替代计划。
  • 幂等回放和零效果响应的实时库存确认。
  • 库存查询失败时保留计划。
  • 目标卡过期、账号不可用,以及阈值策略与临期计划并存。
  • 管理端计划设置、展示和取消交互。

已通过后端全量测试、相关 race 测试、前端 ESLint、类型检查、组件测试和生产构建。

English version

Motivation

OpenAI reset credits can currently be used manually or automatically based on 5h/7d usage thresholds, but a specific expiring credit cannot be scheduled in advance.

Unused reset credits are wasted. With a long validity period, it is easy to forget about them until the reset opportunity has already expired. This PR lets an administrator select a specific credit and schedule it for use a configurable number of minutes before expiration.

Design

Quota refresh now retrieves and caches the complete reset-credit inventory (credit_id + expires_at). An administrator creates a single-credit plan, and the existing worker consumes that exact credit_id when its execution window is reached. A plan whose execution time has already passed runs immediately.

Credit IDs are ordinary resource identifiers rather than credentials. Using the stable upstream ID avoids expiry-based matching and custom selectors. The cache supports display and plan creation; live upstream inventory remains the source of truth when an ambiguous execution result must be confirmed.

Each OpenAI OAuth parent account may have one plan containing plan_id, credit_id, expires_at, and lead_time_minutes. The default lead time is 60 minutes, with an allowed range of 5 to 10080 minutes. A plan authorizes targeted consumption but does not reserve inventory: threshold automation may continue before the execution window, while the targeted plan takes priority inside that window.

Concurrency and Idempotency

A stable redeem_request_id is derived from account_id + credit_id. Conditional updates on accounts.extra prevent an older worker from clearing a replacement plan.

Replayed or zero-effect results are verified against live inventory. A missing target confirms completion; a target that remains available produces OPENAI_AUTO_RESET_REPLAY_CONFLICT or OPENAI_AUTO_RESET_NO_EFFECT; an unverifiable result keeps the plan for retry.

API, UI, and Database

This PR adds:

  • PUT /api/v1/admin/openai/accounts/:id/reset-credit-expiry-target
  • DELETE /api/v1/admin/openai/accounts/:id/reset-credit-expiry-target
  • The optional rate_limit_reset_credits.credits[].id field.
  • Admin controls for scheduling, reviewing, and cancelling a plan.

No tables, columns, indexes, or migrations are added. All new state reuses the existing accounts.extra JSONB column, with per-key CAS updates protecting plan replacement and worker finalization.

Related Work

Tests

Coverage includes plan lifecycle, immediate execution, targeted consumption, multi-instance idempotency, CAS replacement protection, replay and zero-effect verification, query failures, expired targets, unavailable accounts, threshold-plan interaction, and the admin UI.

Backend tests, targeted race tests, frontend linting, type checking, component tests, and the production build all pass.

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