fix(codex): support dynamic quota windows

This commit is contained in:
elky
2026-07-17 20:18:04 +08:00
parent 664c063a06
commit f65ed2795c
22 changed files with 1234 additions and 365 deletions
@@ -19,6 +19,7 @@ function createCodexKey(overrides: Partial<PoolStatsKeyInput> = {}): PoolStatsKe
windows: [
{
code: '5h',
window_minutes: 300,
usage: {
request_count: 5,
total_tokens: 2500,
@@ -27,10 +28,11 @@ function createCodexKey(overrides: Partial<PoolStatsKeyInput> = {}): PoolStatsKe
},
{
code: 'weekly',
window_minutes: 10_080,
usage: {
request_count: 0,
total_tokens: 0,
total_cost_usd: '0.00000000',
request_count: 8,
total_tokens: 5000,
total_cost_usd: '0.012',
},
},
],
@@ -54,9 +56,9 @@ describe('poolStatsDisplay', () => {
total_cost_usd: '$0.0045',
})
expect(metricValues(display.groups[1].metrics)).toEqual({
request_count: '0',
total_tokens: '0',
total_cost_usd: '0',
request_count: '8',
total_tokens: '5K',
total_cost_usd: '$0.012',
})
})
@@ -65,7 +67,7 @@ describe('poolStatsDisplay', () => {
createCodexKey({
status_snapshot: {
quota: {
windows: [{ code: '5h', usage: null }],
windows: [{ code: '5h', window_minutes: 300, usage: null }],
},
},
}),
@@ -81,10 +83,48 @@ describe('poolStatsDisplay', () => {
total_tokens: '—',
total_cost_usd: '—',
})
expect(metricValues(display.groups[1].metrics)).toEqual({
request_count: '—',
total_tokens: '—',
total_cost_usd: '—',
expect(display.groups).toHaveLength(1)
})
it('builds monthly stats from the actual quota window and ignores zero placeholders', () => {
const display = buildPoolStatsDisplay(
createCodexKey({
status_snapshot: {
quota: {
windows: [
{
code: 'monthly',
label: '月',
window_minutes: 43_800,
usage: {
request_count: 12,
total_tokens: 3456,
total_cost_usd: '0.125',
},
},
{
code: 'weekly',
label: '周',
window_minutes: 0,
usage: {
request_count: 99,
},
},
],
},
},
}),
'codex',
'current_cycle',
)
expect(display.kind).toBe('codex_cycle')
if (display.kind !== 'codex_cycle') throw new Error('expected codex cycle display')
expect(display.groups.map(group => group.label)).toEqual(['月'])
expect(metricValues(display.groups[0].metrics)).toEqual({
request_count: '12',
total_tokens: '3.5K',
total_cost_usd: '$0.125',
})
})
@@ -1,10 +1,11 @@
import type { QuotaWindowUsageSnapshot } from '@/api/endpoints/types/statusSnapshot'
import type { PoolManagementStatsMode } from '@/features/pool/utils/poolManagementState'
import { getCodexQuotaWindowPresentation } from '@/utils/codexQuotaWindow'
import { formatCompactNumber } from '@/utils/format'
export type PoolStatsMetricKey = 'request_count' | 'total_tokens' | 'total_cost_usd'
export type PoolStatsDisplayKind = 'account_total' | 'codex_cycle'
export type PoolCodexCycleWindowCode = '5h' | 'weekly'
export type PoolCodexCycleWindowCode = string
export interface PoolStatsKeyInput {
request_count?: number | null
@@ -14,6 +15,9 @@ export interface PoolStatsKeyInput {
quota?: {
windows?: Array<{
code?: string | null
label?: string | null
scope?: string | null
window_minutes?: number | null
usage?: QuotaWindowUsageSnapshot | null
} | null> | null
} | null
@@ -25,6 +29,7 @@ export interface PoolStatsMetric {
label: string
value: string
missing: boolean
numericValue?: number | null
}
export interface PoolAccountTotalStatsDisplay {
@@ -46,10 +51,6 @@ export interface PoolCodexCycleStatsDisplay {
export type PoolStatsDisplay = PoolAccountTotalStatsDisplay | PoolCodexCycleStatsDisplay
const MISSING_STAT_VALUE = '—'
const CODEX_CYCLE_WINDOWS: Array<{ code: PoolCodexCycleWindowCode, label: string }> = [
{ code: '5h', label: '5H' },
{ code: 'weekly', label: '周' },
]
export function isCodexProviderType(providerType: string | null | undefined): boolean {
return String(providerType || '').trim().toLowerCase() === 'codex'
@@ -103,12 +104,14 @@ function createMetric(
key: PoolStatsMetricKey,
label: string,
value: string | null,
numericValue?: number | null,
): PoolStatsMetric {
return {
key,
label,
value: value ?? MISSING_STAT_VALUE,
missing: value == null,
numericValue: numericValue ?? null,
}
}
@@ -116,17 +119,40 @@ function normalizeWindowCode(value: unknown): string {
return String(value || '').trim().toLowerCase()
}
function getQuotaWindowUsage(
key: PoolStatsKeyInput,
code: PoolCodexCycleWindowCode,
): QuotaWindowUsageSnapshot | null {
function getCodexCycleStatsGroups(key: PoolStatsKeyInput): PoolCodexCycleStatsGroup[] {
const windows = key.status_snapshot?.quota?.windows
if (!Array.isArray(windows)) return null
if (!Array.isArray(windows)) return []
const window = windows.find(item => normalizeWindowCode(item?.code) === code)
return window?.usage ?? null
const seenCodes = new Set<string>()
return windows
.map((window) => {
if (!window) return null
const code = normalizeWindowCode(window.code)
const scope = String(window.scope || 'account').trim().toLowerCase()
if (!code || scope !== 'account' || code.startsWith('spark_') || seenCodes.has(code)) {
return null
}
const presentation = getCodexQuotaWindowPresentation({
code,
label: window.label,
scope,
window_minutes: window.window_minutes,
})
if (!presentation) return null
seenCodes.add(code)
return {
code,
label: presentation.label,
sortOrder: presentation.sortOrder,
metrics: buildCycleMetrics(window.usage ?? null),
}
})
.filter((group): group is PoolCodexCycleStatsGroup & { sortOrder: number } => group != null)
.sort((left, right) => left.sortOrder - right.sortOrder)
.map(({ sortOrder: _sortOrder, ...group }) => group)
}
function buildAccountTotalMetrics(key: PoolStatsKeyInput): PoolStatsMetric[] {
return [
createMetric('request_count', '请求', formatPoolStatInteger(key.request_count)),
@@ -136,10 +162,28 @@ function buildAccountTotalMetrics(key: PoolStatsKeyInput): PoolStatsMetric[] {
}
function buildCycleMetrics(usage: QuotaWindowUsageSnapshot | null): PoolStatsMetric[] {
const requestCount = usage?.request_count == null ? null : Number(usage.request_count)
const totalTokens = usage?.total_tokens == null ? null : Number(usage.total_tokens)
const totalCostUsd = usage?.total_cost_usd == null ? null : Number(usage.total_cost_usd)
return [
createMetric('request_count', '请求', formatCycleInteger(usage?.request_count)),
createMetric('total_tokens', 'Token', formatCycleTokenCount(usage?.total_tokens)),
createMetric('total_cost_usd', '费用', formatCycleUsd(usage?.total_cost_usd)),
createMetric(
'request_count',
'请求',
formatCycleInteger(usage?.request_count),
Number.isFinite(requestCount) ? Math.max(requestCount ?? 0, 0) : null,
),
createMetric(
'total_tokens',
'Token',
formatCycleTokenCount(usage?.total_tokens),
Number.isFinite(totalTokens) ? Math.max(totalTokens ?? 0, 0) : null,
),
createMetric(
'total_cost_usd',
'费用',
formatCycleUsd(usage?.total_cost_usd),
Number.isFinite(totalCostUsd) ? Math.max(totalCostUsd ?? 0, 0) : null,
),
]
}
@@ -157,10 +201,7 @@ export function buildCodexCycleStatsDisplay(
): PoolCodexCycleStatsDisplay {
return {
kind: 'codex_cycle',
groups: CODEX_CYCLE_WINDOWS.map(window => ({
...window,
metrics: buildCycleMetrics(getQuotaWindowUsage(key, window.code)),
})),
groups: getCodexCycleStatsGroups(key),
}
}