Revert "fix(usage): 修正 OpenAI 5h 用量百分比语义"

This reverts commit b65dde634bf7a5338f22b46ad0f9d98a197526fc.
This commit is contained in:
ghostg00 2026-06-03 11:37:55 +08:00 committed by jake.gao
parent aa69e3947d
commit a8ffb052ca
6 changed files with 22 additions and 96 deletions

View File

@ -132,7 +132,7 @@ func TestAccountTestService_OpenAISuccessPersistsSnapshotFromHeaders(t *testing.
require.Len(t, upstream.requests, 1) require.Len(t, upstream.requests, 1)
require.Equal(t, HTTPUpstreamProfileOpenAI, HTTPUpstreamProfileFromContext(upstream.requests[0].Context())) require.Equal(t, HTTPUpstreamProfileOpenAI, HTTPUpstreamProfileFromContext(upstream.requests[0].Context()))
require.NotEmpty(t, repo.updatedExtra) require.NotEmpty(t, repo.updatedExtra)
require.Equal(t, 58.0, repo.updatedExtra["codex_5h_used_percent"]) require.Equal(t, 42.0, repo.updatedExtra["codex_5h_used_percent"])
require.Equal(t, 88.0, repo.updatedExtra["codex_7d_used_percent"]) require.Equal(t, 88.0, repo.updatedExtra["codex_7d_used_percent"])
require.Contains(t, recorder.Body.String(), "test_complete") require.Contains(t, recorder.Body.String(), "test_complete")
} }
@ -170,7 +170,7 @@ func TestAccountTestService_OpenAI429PersistsSnapshotAndRateLimitState(t *testin
resp.Header.Set("x-codex-primary-used-percent", "100") resp.Header.Set("x-codex-primary-used-percent", "100")
resp.Header.Set("x-codex-primary-reset-after-seconds", "604800") resp.Header.Set("x-codex-primary-reset-after-seconds", "604800")
resp.Header.Set("x-codex-primary-window-minutes", "10080") resp.Header.Set("x-codex-primary-window-minutes", "10080")
resp.Header.Set("x-codex-secondary-used-percent", "0") resp.Header.Set("x-codex-secondary-used-percent", "100")
resp.Header.Set("x-codex-secondary-reset-after-seconds", "18000") resp.Header.Set("x-codex-secondary-reset-after-seconds", "18000")
resp.Header.Set("x-codex-secondary-window-minutes", "300") resp.Header.Set("x-codex-secondary-window-minutes", "300")

View File

@ -73,7 +73,7 @@ func TestExtractOpenAICodexProbeUpdatesAccepts429WithCodexHeaders(t *testing.T)
headers.Set("x-codex-primary-used-percent", "100") headers.Set("x-codex-primary-used-percent", "100")
headers.Set("x-codex-primary-reset-after-seconds", "604800") headers.Set("x-codex-primary-reset-after-seconds", "604800")
headers.Set("x-codex-primary-window-minutes", "10080") headers.Set("x-codex-primary-window-minutes", "10080")
headers.Set("x-codex-secondary-used-percent", "0") headers.Set("x-codex-secondary-used-percent", "100")
headers.Set("x-codex-secondary-reset-after-seconds", "18000") headers.Set("x-codex-secondary-reset-after-seconds", "18000")
headers.Set("x-codex-secondary-window-minutes", "300") headers.Set("x-codex-secondary-window-minutes", "300")
@ -92,33 +92,6 @@ func TestExtractOpenAICodexProbeUpdatesAccepts429WithCodexHeaders(t *testing.T)
} }
} }
func TestBuildCodexUsageProgressFromExtra_UsesCanonicalUsedPercent(t *testing.T) {
t.Parallel()
now := time.Date(2026, 5, 30, 7, 4, 9, 0, time.UTC)
extra := map[string]any{
"codex_5h_used_percent": 94.0,
"codex_5h_reset_at": now.Add(2 * time.Hour).Format(time.RFC3339),
"codex_7d_used_percent": 93.0,
"codex_7d_reset_at": now.Add(5 * 24 * time.Hour).Format(time.RFC3339),
}
fiveHour := buildCodexUsageProgressFromExtra(extra, "5h", now)
if fiveHour == nil {
t.Fatal("expected non-nil 5h progress")
}
if fiveHour.Utilization != 94.0 {
t.Fatalf("5h Utilization = %v, want 94", fiveHour.Utilization)
}
sevenDay := buildCodexUsageProgressFromExtra(extra, "7d", now)
if sevenDay == nil {
t.Fatal("expected non-nil 7d progress")
}
if sevenDay.Utilization != 93.0 {
t.Fatalf("7d Utilization = %v, want 93", sevenDay.Utilization)
}
}
func TestAccountUsageService_PersistOpenAICodexProbeSnapshotOnlyUpdatesExtra(t *testing.T) { func TestAccountUsageService_PersistOpenAICodexProbeSnapshotOnlyUpdatesExtra(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -126,19 +126,6 @@ type NormalizedCodexLimits struct {
Window7dMinutes *int Window7dMinutes *int
} }
func normalizeCodexFiveHourUsedPercent(raw *float64) *float64 {
if raw == nil {
return nil
}
// OpenAI's 5h Codex quota header is remaining%, despite the upstream header
// name saying "used"; the canonical codex_5h_used_percent field stores used%.
used := 100 - *raw
if used < 0 {
used = 0
}
return &used
}
// Normalize converts primary/secondary fields to canonical 5h/7d fields. // Normalize converts primary/secondary fields to canonical 5h/7d fields.
// Strategy: Compare window_minutes to determine which is 5h vs 7d. // Strategy: Compare window_minutes to determine which is 5h vs 7d.
// Returns nil if snapshot is nil or has no useful data. // Returns nil if snapshot is nil or has no useful data.
@ -197,7 +184,7 @@ func (s *OpenAICodexUsageSnapshot) Normalize() *NormalizedCodexLimits {
// Assign values // Assign values
if use5hFromPrimary { if use5hFromPrimary {
result.Used5hPercent = normalizeCodexFiveHourUsedPercent(s.PrimaryUsedPercent) result.Used5hPercent = s.PrimaryUsedPercent
result.Reset5hSeconds = s.PrimaryResetAfterSeconds result.Reset5hSeconds = s.PrimaryResetAfterSeconds
result.Window5hMinutes = s.PrimaryWindowMinutes result.Window5hMinutes = s.PrimaryWindowMinutes
result.Used7dPercent = s.SecondaryUsedPercent result.Used7dPercent = s.SecondaryUsedPercent
@ -207,7 +194,7 @@ func (s *OpenAICodexUsageSnapshot) Normalize() *NormalizedCodexLimits {
result.Used7dPercent = s.PrimaryUsedPercent result.Used7dPercent = s.PrimaryUsedPercent
result.Reset7dSeconds = s.PrimaryResetAfterSeconds result.Reset7dSeconds = s.PrimaryResetAfterSeconds
result.Window7dMinutes = s.PrimaryWindowMinutes result.Window7dMinutes = s.PrimaryWindowMinutes
result.Used5hPercent = normalizeCodexFiveHourUsedPercent(s.SecondaryUsedPercent) result.Used5hPercent = s.SecondaryUsedPercent
result.Reset5hSeconds = s.SecondaryResetAfterSeconds result.Reset5hSeconds = s.SecondaryResetAfterSeconds
result.Window5hMinutes = s.SecondaryWindowMinutes result.Window5hMinutes = s.SecondaryWindowMinutes
} }

View File

@ -104,40 +104,6 @@ func TestBuildCodexUsageExtraUpdates_UsesSnapshotUpdatedAt(t *testing.T) {
} }
} }
func TestBuildCodexUsageExtraUpdates_NormalizesFiveHourRemainingToUsedPercent(t *testing.T) {
primaryUsed := 93.0
primaryReset := 86400
primaryWindow := 10080
secondaryRemaining := 6.0
secondaryReset := 3600
secondaryWindow := 300
snapshot := &OpenAICodexUsageSnapshot{
PrimaryUsedPercent: &primaryUsed,
PrimaryResetAfterSeconds: &primaryReset,
PrimaryWindowMinutes: &primaryWindow,
SecondaryUsedPercent: &secondaryRemaining,
SecondaryResetAfterSeconds: &secondaryReset,
SecondaryWindowMinutes: &secondaryWindow,
UpdatedAt: "2026-05-30T07:04:09Z",
}
updates := buildCodexUsageExtraUpdates(snapshot, time.Time{})
if updates == nil {
t.Fatal("expected non-nil updates")
}
if got := updates["codex_secondary_used_percent"]; got != 6.0 {
t.Fatalf("codex_secondary_used_percent = %v, want raw upstream value 6", got)
}
if got := updates["codex_5h_used_percent"]; got != 94.0 {
t.Fatalf("codex_5h_used_percent = %v, want 94", got)
}
if got := updates["codex_7d_used_percent"]; got != 93.0 {
t.Fatalf("codex_7d_used_percent = %v, want 93", got)
}
}
func TestBuildCodexUsageExtraUpdates_FallbackToNowWhenUpdatedAtInvalid(t *testing.T) { func TestBuildCodexUsageExtraUpdates_FallbackToNowWhenUpdatedAtInvalid(t *testing.T) {
primaryUsed := 15.0 primaryUsed := 15.0
primaryReset := 30 primaryReset := 30

View File

@ -1774,7 +1774,7 @@ func TestOpenAIUpdateCodexUsageSnapshotFromHeaders(t *testing.T) {
select { select {
case updates := <-repo.updateExtraCalls: case updates := <-repo.updateExtraCalls:
require.Equal(t, 88.0, updates["codex_5h_used_percent"]) require.Equal(t, 12.0, updates["codex_5h_used_percent"])
require.Equal(t, 34.0, updates["codex_7d_used_percent"]) require.Equal(t, 34.0, updates["codex_7d_used_percent"])
require.Equal(t, 600, updates["codex_5h_reset_after_seconds"]) require.Equal(t, 600, updates["codex_5h_reset_after_seconds"])
require.Equal(t, 86400, updates["codex_7d_reset_after_seconds"]) require.Equal(t, 86400, updates["codex_7d_reset_after_seconds"])

View File

@ -51,7 +51,7 @@ func TestCalculateOpenAI429ResetTime_5hExhausted(t *testing.T) {
headers.Set("x-codex-primary-used-percent", "50") headers.Set("x-codex-primary-used-percent", "50")
headers.Set("x-codex-primary-reset-after-seconds", "500000") headers.Set("x-codex-primary-reset-after-seconds", "500000")
headers.Set("x-codex-primary-window-minutes", "10080") // 7 days headers.Set("x-codex-primary-window-minutes", "10080") // 7 days
headers.Set("x-codex-secondary-used-percent", "0") headers.Set("x-codex-secondary-used-percent", "100")
headers.Set("x-codex-secondary-reset-after-seconds", "3600") // 1 hour headers.Set("x-codex-secondary-reset-after-seconds", "3600") // 1 hour
headers.Set("x-codex-secondary-window-minutes", "300") // 5 hours headers.Set("x-codex-secondary-window-minutes", "300") // 5 hours
@ -122,7 +122,7 @@ func TestCalculateOpenAI429ResetTime_ReversedWindowOrder(t *testing.T) {
// Test when OpenAI sends primary as 5h and secondary as 7d (reversed) // Test when OpenAI sends primary as 5h and secondary as 7d (reversed)
headers := http.Header{} headers := http.Header{}
headers.Set("x-codex-primary-used-percent", "0") // This is 5h remaining% headers.Set("x-codex-primary-used-percent", "100") // This is 5h
headers.Set("x-codex-primary-reset-after-seconds", "3600") // 1 hour headers.Set("x-codex-primary-reset-after-seconds", "3600") // 1 hour
headers.Set("x-codex-primary-window-minutes", "300") // 5 hours - smaller! headers.Set("x-codex-primary-window-minutes", "300") // 5 hours - smaller!
headers.Set("x-codex-secondary-used-percent", "50") headers.Set("x-codex-secondary-used-percent", "50")
@ -180,7 +180,7 @@ func TestHandle429_OpenAIPersistsCodexSnapshotImmediately(t *testing.T) {
headers.Set("x-codex-primary-used-percent", "100") headers.Set("x-codex-primary-used-percent", "100")
headers.Set("x-codex-primary-reset-after-seconds", "604800") headers.Set("x-codex-primary-reset-after-seconds", "604800")
headers.Set("x-codex-primary-window-minutes", "10080") headers.Set("x-codex-primary-window-minutes", "10080")
headers.Set("x-codex-secondary-used-percent", "0") headers.Set("x-codex-secondary-used-percent", "100")
headers.Set("x-codex-secondary-reset-after-seconds", "18000") headers.Set("x-codex-secondary-reset-after-seconds", "18000")
headers.Set("x-codex-secondary-window-minutes", "300") headers.Set("x-codex-secondary-window-minutes", "300")
@ -224,7 +224,7 @@ func TestNormalizedCodexLimits(t *testing.T) {
pUsed := 100.0 pUsed := 100.0
pReset := 384607 pReset := 384607
pWindow := 10080 pWindow := 10080
sRemaining := 3.0 sUsed := 3.0
sReset := 17369 sReset := 17369
sWindow := 300 sWindow := 300
@ -232,7 +232,7 @@ func TestNormalizedCodexLimits(t *testing.T) {
PrimaryUsedPercent: &pUsed, PrimaryUsedPercent: &pUsed,
PrimaryResetAfterSeconds: &pReset, PrimaryResetAfterSeconds: &pReset,
PrimaryWindowMinutes: &pWindow, PrimaryWindowMinutes: &pWindow,
SecondaryUsedPercent: &sRemaining, SecondaryUsedPercent: &sUsed,
SecondaryResetAfterSeconds: &sReset, SecondaryResetAfterSeconds: &sReset,
SecondaryWindowMinutes: &sWindow, SecondaryWindowMinutes: &sWindow,
} }
@ -249,8 +249,8 @@ func TestNormalizedCodexLimits(t *testing.T) {
if normalized.Reset7dSeconds == nil || *normalized.Reset7dSeconds != 384607 { if normalized.Reset7dSeconds == nil || *normalized.Reset7dSeconds != 384607 {
t.Errorf("expected Reset7dSeconds=384607, got %v", normalized.Reset7dSeconds) t.Errorf("expected Reset7dSeconds=384607, got %v", normalized.Reset7dSeconds)
} }
if normalized.Used5hPercent == nil || *normalized.Used5hPercent != 97.0 { if normalized.Used5hPercent == nil || *normalized.Used5hPercent != 3.0 {
t.Errorf("expected Used5hPercent=97, got %v", normalized.Used5hPercent) t.Errorf("expected Used5hPercent=3, got %v", normalized.Used5hPercent)
} }
if normalized.Reset5hSeconds == nil || *normalized.Reset5hSeconds != 17369 { if normalized.Reset5hSeconds == nil || *normalized.Reset5hSeconds != 17369 {
t.Errorf("expected Reset5hSeconds=17369, got %v", normalized.Reset5hSeconds) t.Errorf("expected Reset5hSeconds=17369, got %v", normalized.Reset5hSeconds)
@ -338,11 +338,11 @@ func TestRateLimitService_HandleUpstreamError_403FallsBackToRawBody(t *testing.T
func TestNormalizedCodexLimits_OnlySecondaryData(t *testing.T) { func TestNormalizedCodexLimits_OnlySecondaryData(t *testing.T) {
// Test when only secondary has data, no window_minutes // Test when only secondary has data, no window_minutes
sRemaining := 60.0 sUsed := 60.0
sReset := 3000 sReset := 3000
snapshot := &OpenAICodexUsageSnapshot{ snapshot := &OpenAICodexUsageSnapshot{
SecondaryUsedPercent: &sRemaining, SecondaryUsedPercent: &sUsed,
SecondaryResetAfterSeconds: &sReset, SecondaryResetAfterSeconds: &sReset,
// No window_minutes, no primary data // No window_minutes, no primary data
} }
@ -354,8 +354,8 @@ func TestNormalizedCodexLimits_OnlySecondaryData(t *testing.T) {
// Legacy assumption: primary=7d, secondary=5h // Legacy assumption: primary=7d, secondary=5h
// So secondary goes to 5h // So secondary goes to 5h
if normalized.Used5hPercent == nil || *normalized.Used5hPercent != 40.0 { if normalized.Used5hPercent == nil || *normalized.Used5hPercent != 60.0 {
t.Errorf("expected Used5hPercent=40, got %v", normalized.Used5hPercent) t.Errorf("expected Used5hPercent=60, got %v", normalized.Used5hPercent)
} }
if normalized.Reset5hSeconds == nil || *normalized.Reset5hSeconds != 3000 { if normalized.Reset5hSeconds == nil || *normalized.Reset5hSeconds != 3000 {
t.Errorf("expected Reset5hSeconds=3000, got %v", normalized.Reset5hSeconds) t.Errorf("expected Reset5hSeconds=3000, got %v", normalized.Reset5hSeconds)
@ -370,13 +370,13 @@ func TestNormalizedCodexLimits_BothDataNoWindowMinutes(t *testing.T) {
// Test when both have data but no window_minutes // Test when both have data but no window_minutes
pUsed := 100.0 pUsed := 100.0
pReset := 400000 pReset := 400000
sRemaining := 30.0 sUsed := 50.0
sReset := 10000 sReset := 10000
snapshot := &OpenAICodexUsageSnapshot{ snapshot := &OpenAICodexUsageSnapshot{
PrimaryUsedPercent: &pUsed, PrimaryUsedPercent: &pUsed,
PrimaryResetAfterSeconds: &pReset, PrimaryResetAfterSeconds: &pReset,
SecondaryUsedPercent: &sRemaining, SecondaryUsedPercent: &sUsed,
SecondaryResetAfterSeconds: &sReset, SecondaryResetAfterSeconds: &sReset,
// No window_minutes // No window_minutes
} }
@ -393,8 +393,8 @@ func TestNormalizedCodexLimits_BothDataNoWindowMinutes(t *testing.T) {
if normalized.Reset7dSeconds == nil || *normalized.Reset7dSeconds != 400000 { if normalized.Reset7dSeconds == nil || *normalized.Reset7dSeconds != 400000 {
t.Errorf("expected Reset7dSeconds=400000, got %v", normalized.Reset7dSeconds) t.Errorf("expected Reset7dSeconds=400000, got %v", normalized.Reset7dSeconds)
} }
if normalized.Used5hPercent == nil || *normalized.Used5hPercent != 70.0 { if normalized.Used5hPercent == nil || *normalized.Used5hPercent != 50.0 {
t.Errorf("expected Used5hPercent=70, got %v", normalized.Used5hPercent) t.Errorf("expected Used5hPercent=50, got %v", normalized.Used5hPercent)
} }
if normalized.Reset5hSeconds == nil || *normalized.Reset5hSeconds != 10000 { if normalized.Reset5hSeconds == nil || *normalized.Reset5hSeconds != 10000 {
t.Errorf("expected Reset5hSeconds=10000, got %v", normalized.Reset5hSeconds) t.Errorf("expected Reset5hSeconds=10000, got %v", normalized.Reset5hSeconds)
@ -425,7 +425,7 @@ func TestCalculateOpenAI429ResetTime_UserProvidedScenario(t *testing.T) {
// This is the exact scenario from the user: // This is the exact scenario from the user:
// codex_7d_used_percent: 100 // codex_7d_used_percent: 100
// codex_7d_reset_after_seconds: 384607 (约4.5天后重置) // codex_7d_reset_after_seconds: 384607 (约4.5天后重置)
// codex_5h_used_percent: 97 (from upstream 3% remaining) // codex_5h_used_percent: 3
// codex_5h_reset_after_seconds: 17369 (约4.8小时后重置) // codex_5h_reset_after_seconds: 17369 (约4.8小时后重置)
svc := &RateLimitService{} svc := &RateLimitService{}