mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 18:07:45 +00:00
fix(native-chat): show when a Codex usage limit resets, like Claude (#40992)
* fix(codex): keep the reset time when a usage limit rejects before the report reaches 100% Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(codex): read the usage-limit error code from the renamed error info * fix(codex): skip windows that already reset when blaming a nearly spent one --------- Co-authored-by: moncha3 <moncha3@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
moncha3
parent
7a3aacef0b
commit
71404c99be
@@ -62,6 +62,33 @@ describe('a Codex usage limit', () => {
|
||||
])
|
||||
translator.dispose()
|
||||
})
|
||||
it('takes the reset from a nearly spent window when the rejection outruns the last report', () => {
|
||||
const limits = new CodexUsageLimits()
|
||||
const rejectedWith = (primaryPercent: number, code = 'usageLimitExceeded') => {
|
||||
limits.observe('notification:account/rateLimits/updated', {
|
||||
rateLimits: {
|
||||
limitId: 'codex',
|
||||
primary: { usedPercent: primaryPercent, windowDurationMins: 300, resetsAt: RESETS_AT_S },
|
||||
secondary: { usedPercent: 70, windowDurationMins: 10080, resetsAt: RESETS_AT_S + 3600 }
|
||||
}
|
||||
})
|
||||
return limits.failure({ error: { codexErrorInfo: code, message: CODEX_WORDS } })?.usageLimit
|
||||
}
|
||||
expect(rejectedWith(99)).toEqual({ window: 'session', resetsAt: RESETS_AT_S * 1000 })
|
||||
expect(rejectedWith(50)).toBeUndefined()
|
||||
expect(rejectedWith(99, 'rateLimitExceeded')).toBeUndefined()
|
||||
|
||||
limits.observe('notification:account/rateLimits/updated', {
|
||||
rateLimits: {
|
||||
primary: { usedPercent: 99, windowDurationMins: 300, resetsAt: 1_000_000 },
|
||||
secondary: { usedPercent: 95, windowDurationMins: 10080, resetsAt: RESETS_AT_S }
|
||||
}
|
||||
})
|
||||
expect(
|
||||
limits.failure({ error: { codexErrorInfo: 'usageLimitExceeded', message: CODEX_WORDS } })
|
||||
?.usageLimit
|
||||
).toEqual({ window: 'weekly', resetsAt: RESETS_AT_S * 1000 })
|
||||
})
|
||||
it('merges null account updates and keeps unnamed exhausted windows in reset calculation', () => {
|
||||
const limits = new CodexUsageLimits()
|
||||
const observe = (rateLimits: Record<string, unknown>) =>
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
|
||||
const RATE_LIMITS_UPDATED = 'notification:account/rateLimits/updated'
|
||||
const USAGE_LIMIT_ERRORS = ['usageLimitExceeded', 'rateLimitExceeded']
|
||||
/** The last report can trail a rejection by the request that went over, so a window this full counts as the spent one. */
|
||||
const NEARLY_SPENT_PERCENT = 90
|
||||
|
||||
type CodexAccountRateLimits = CodexRateLimitWindowsSnapshot & {
|
||||
rateLimitReachedType?: unknown
|
||||
@@ -62,7 +64,7 @@ export class CodexUsageLimits {
|
||||
const text = [message, additional && additional !== message ? additional : null]
|
||||
.filter((part): part is string => part !== null)
|
||||
.join('\n')
|
||||
const limit = this.limit()
|
||||
const limit = this.limit(info.error === 'usageLimitExceeded')
|
||||
return agentSessionFailureFact('usageLimitReached', {
|
||||
...(limit ? { usageLimit: limit } : {}),
|
||||
detail: providerDiagnostic(text, 'person')
|
||||
@@ -70,21 +72,16 @@ export class CodexUsageLimits {
|
||||
}
|
||||
|
||||
/** All spent windows must reset; report the latest only if every reset is known. */
|
||||
private limit(): AgentSessionUsageLimit | null {
|
||||
private limit(rejected: boolean): AgentSessionUsageLimit | null {
|
||||
const monthlyPlan = this.snapshot.planType === 'free' || this.snapshot.planType === 'go'
|
||||
const spent = (
|
||||
const reported = (
|
||||
[
|
||||
['primary', monthlyPlan ? 43200 : 300],
|
||||
['secondary', 10080]
|
||||
] as const
|
||||
).flatMap(([position, fallback]) => {
|
||||
const raw = this.snapshot[position]
|
||||
if (
|
||||
!raw ||
|
||||
typeof raw.usedPercent !== 'number' ||
|
||||
!Number.isFinite(raw.usedPercent) ||
|
||||
raw.usedPercent < 100
|
||||
) {
|
||||
if (!raw || typeof raw.usedPercent !== 'number' || !Number.isFinite(raw.usedPercent)) {
|
||||
return []
|
||||
}
|
||||
const duration =
|
||||
@@ -99,8 +96,19 @@ export class CodexUsageLimits {
|
||||
? ('weekly' as const)
|
||||
: ('session' as const)
|
||||
: undefined
|
||||
return [{ window, resetsAt: raw.resetsAt }]
|
||||
return [{ window, resetsAt: raw.resetsAt, usedPercent: raw.usedPercent }]
|
||||
})
|
||||
// A window that already reset cannot be the one that just rejected.
|
||||
const live = reported.filter(
|
||||
(entry) => typeof entry.resetsAt !== 'number' || entry.resetsAt * 1000 > this.now()
|
||||
)
|
||||
const fullest = Math.max(...live.map((entry) => entry.usedPercent))
|
||||
const spent =
|
||||
rejected &&
|
||||
fullest >= NEARLY_SPENT_PERCENT &&
|
||||
!reported.some((entry) => entry.usedPercent >= 100)
|
||||
? live.filter((entry) => entry.usedPercent >= fullest)
|
||||
: reported.filter((entry) => entry.usedPercent >= 100)
|
||||
const reached = this.snapshot.rateLimitReachedType
|
||||
const credits = typeof reached === 'string' && reached.endsWith('credits_depleted')
|
||||
const window: AgentSessionUsageLimitWindow | undefined = credits
|
||||
|
||||
Reference in New Issue
Block a user