feat(openai-quota): query + reset rate-limit credits for OpenAI accounts

Adds an admin-side action that mirrors the Codex Desktop "rate-limit reset"
flow against chatgpt.com upstream for OpenAI OAuth accounts.

Backend
- OpenAIQuotaService.QueryUsage / ResetCredit hit /wham/usage and
  /wham/rate-limit-reset-credits/consume with the Codex Desktop header set,
  reusing OpenAITokenProvider for refreshed tokens and PrivacyClientFactory
  for the impersonated Chrome TLS fingerprint.
- Honors the account's configured proxy by reading the eager-loaded
  account.Proxy directly (falls back to proxyRepo only when missing).
- GET /api/v1/admin/openai/accounts/:id/quota
  POST /api/v1/admin/openai/accounts/:id/reset-quota
- Wire DI for the new service + handler dependency.

Frontend
- OpenAIQuotaResetCell renders a single action row in AccountUsageCell's
  OpenAI section: the existing local "查询" (active sampling) is injected
  via #pre-actions, alongside a "次数 N" button that doubles as the
  upstream query trigger and the available-credit indicator, and a "重置"
  button that consumes one credit.
- No duplicate 5h/7d window display; the local UsageProgressBar owns those
  bars to avoid confusion.
This commit is contained in:
shaw
2026-06-16 16:55:07 +08:00
parent f069c9ae00
commit b816949291
10 changed files with 709 additions and 27 deletions
+73 -1
View File
@@ -705,6 +705,76 @@ export async function setPrivacy(id: number): Promise<Account> {
return data
}
/**
* OpenAI / Codex rate-limit reset feature: query and reset upstream usage.
*/
export interface OpenAIRateLimitWindow {
used_percent: number
limit_window_seconds: number
reset_after_seconds: number
reset_at: number
}
export interface OpenAIRateLimit {
allowed: boolean
limit_reached: boolean
primary_window?: OpenAIRateLimitWindow | null
secondary_window?: OpenAIRateLimitWindow | null
}
export interface OpenAIAdditionalRateLimit {
limit_name: string
metered_feature: string
rate_limit?: OpenAIRateLimit | null
}
export interface OpenAIRateLimitResetCredits {
available_count: number
}
export interface OpenAIQuotaUsage {
user_id?: string
account_id?: string
email?: string
plan_type?: string
rate_limit?: OpenAIRateLimit | null
additional_rate_limits?: OpenAIAdditionalRateLimit[]
rate_limit_reset_credits?: OpenAIRateLimitResetCredits | null
fetched_at: number
}
export interface OpenAIQuotaResetCredit {
id?: string
reset_type?: string
status?: string
granted_at?: string
expires_at?: string
redeem_started_at?: string
redeemed_at?: string
}
export interface OpenAIQuotaResetResult {
code: string
credit?: OpenAIQuotaResetCredit | null
windows_reset: number
}
/**
* Query OpenAI/Codex rate-limit usage for an OAuth account.
*/
export async function queryOpenAIQuota(id: number): Promise<OpenAIQuotaUsage> {
const { data } = await apiClient.get<OpenAIQuotaUsage>(`/admin/openai/accounts/${id}/quota`)
return data
}
/**
* Consume one rate-limit-reset credit for an OpenAI/Codex OAuth account.
*/
export async function resetOpenAIQuota(id: number): Promise<OpenAIQuotaResetResult> {
const { data } = await apiClient.post<OpenAIQuotaResetResult>(`/admin/openai/accounts/${id}/reset-quota`)
return data
}
export const accountsAPI = {
list,
listWithEtag,
@@ -746,7 +816,9 @@ export const accountsAPI = {
batchClearError,
batchRefresh,
setPrivacy,
revertProxyFallback
revertProxyFallback,
queryOpenAIQuota,
resetOpenAIQuota
}
export default accountsAPI