mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-30 16:23:31 +00:00
fix(openai): harden reset-credit refresh and account recovery
Review follow-ups on the reset-credit caching flow: - Recover account state BEFORE (and independently of) the reset-credit display cache. A failed cache refresh could previously abort the run and leave the account rate-limited — the very reason the credit was spent (#3672 / #3740). The recovered account row is now returned even when the cache refresh fails. - Run the post-reset bookkeeping on a detached, time-boxed context and give the panel reset call a larger timeout. A client abort no longer strands a consumed (non-refundable) credit with an unrecovered account, and the chained upstream calls can no longer exceed the client timeout and invite a retry that spends a second credit. - Persist the reset-credit snapshot through POST /accounts/:id/quota/refresh instead of a side-effecting GET flag, so the write is covered by the audit middleware. A rejected snapshot write now degrades to cache_persisted=false instead of turning a successful upstream read into a 502 that left the card without a credit count and the reset button permanently disabled. - Reject snapshots whose positive count carries no expiration entries, and drop expired credits (clamping the count) when rehydrating, so a stale cache can no longer light up the reset button. - Keep nil quota / rate-limit services nil in the handler's interface fields; storing a nil *Service made the "not enabled" guards non-nil. - Time-box the usage-refresh suppression and reuse handleAccountUpdated so the patched row also enters the auto-refresh silent window.
This commit is contained in:
@@ -844,24 +844,41 @@ export interface OpenAIQuotaResetResult {
|
||||
| 'account_state_refresh_failed'
|
||||
}
|
||||
|
||||
/** Usage payload plus whether the reset-credit snapshot was persisted. */
|
||||
export interface OpenAIQuotaRefreshResult extends OpenAIQuotaUsage {
|
||||
cache_persisted: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Query OpenAI/Codex rate-limit usage for an OAuth account.
|
||||
* Query the upstream quota AND persist the reset-credit snapshot on the account
|
||||
* so the card can be rehydrated without an upstream round-trip. It is a POST
|
||||
* because it writes account state (and must therefore be audited).
|
||||
*
|
||||
* The read-only `GET /admin/openai/accounts/:id/quota` endpoint still exists for
|
||||
* API consumers; the panel always wants the snapshot persisted, so it has no
|
||||
* client binding here.
|
||||
*/
|
||||
export async function queryOpenAIQuota(
|
||||
id: number,
|
||||
options?: { persistResetCredits?: boolean }
|
||||
): Promise<OpenAIQuotaUsage> {
|
||||
const { data } = await apiClient.get<OpenAIQuotaUsage>(`/admin/openai/accounts/${id}/quota`, {
|
||||
params: options?.persistResetCredits === true ? { persist_reset_credits: true } : undefined
|
||||
})
|
||||
export async function refreshOpenAIQuota(id: number): Promise<OpenAIQuotaRefreshResult> {
|
||||
const { data } = await apiClient.post<OpenAIQuotaRefreshResult>(
|
||||
`/admin/openai/accounts/${id}/quota/refresh`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume one rate-limit-reset credit for an OpenAI/Codex OAuth account.
|
||||
*
|
||||
* The credit is non-refundable and the endpoint chains an upstream reset with an
|
||||
* upstream re-query, so it needs a larger budget than the default client
|
||||
* timeout: aborting locally would report a successful consumption as a failure
|
||||
* and invite a retry that spends a second credit.
|
||||
*/
|
||||
export async function resetOpenAIQuota(id: number): Promise<OpenAIQuotaResetResult> {
|
||||
const { data } = await apiClient.post<OpenAIQuotaResetResult>(`/admin/openai/accounts/${id}/reset-quota`)
|
||||
const { data } = await apiClient.post<OpenAIQuotaResetResult>(
|
||||
`/admin/openai/accounts/${id}/reset-quota`,
|
||||
undefined,
|
||||
{ timeout: 90_000 }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -998,7 +1015,7 @@ export const accountsAPI = {
|
||||
batchRefresh,
|
||||
setPrivacy,
|
||||
revertProxyFallback,
|
||||
queryOpenAIQuota,
|
||||
refreshOpenAIQuota,
|
||||
resetOpenAIQuota,
|
||||
createSparkShadow,
|
||||
getUpstreamBillingProbeSettings,
|
||||
|
||||
Reference in New Issue
Block a user