Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions internal/aggregate/aggregate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,34 @@ func TestComputeAggregatesDynamicSevenDayWindow(t *testing.T) {
}
}

func TestComputeAggregatesGenericDurationWindow(t *testing.T) {
now := int64(1_000)
key := quota.WindowName("1d")
results := []quota.Result{
{
Status: quota.StatusOK,
Windows: map[quota.WindowName]quota.Window{
key: {RemainingPct: 80, ResetAtUnix: now + 43_200},
},
},
{
Status: quota.StatusOK,
Windows: map[quota.WindowName]quota.Window{
key: {RemainingPct: 60, ResetAtUnix: now + 43_200},
},
},
}

agg, _ := Compute(results, now, nil)
got, ok := agg[key]
if !ok {
t.Fatal("missing aggregate 1d window")
}
if got.RemainingPct != 70 || got.ExpectedPct != 50 || got.PaceDiff != 20 {
t.Fatalf("aggregate 1d = %#v, want remaining=70 expected=50 pace=20", got)
}
}

func TestComputeGatesDynamic5hWithMatching7dBucket(t *testing.T) {
now := int64(1_000)
fiveHour := quota.WindowName("5h:gpt-5.3-codex-spark")
Expand Down
12 changes: 12 additions & 0 deletions internal/history/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ func (s *Store) UpdateAndGetBurnRates(
// a zero-delta would produce a divide-by-zero below.
continue
}
period := int64(quota.PeriodFor(winName).Seconds())
if period > 0 && dt > period {
// A gap longer than the window cannot distinguish suspended or
// unobserved resets from actual consumption. Cold-start instead.
*prev = WindowState{
LastSeenUnix: nowEpoch,
LastRemainingPct: w.RemainingPct,
LastResetAtUnix: w.ResetAtUnix,
Samples: 1,
}
continue
}

deltaPct, resetOK := computeDelta(prev, w, winName)
if !resetOK {
Expand Down
38 changes: 38 additions & 0 deletions internal/history/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,44 @@ func TestStoreSecondObservationEWMAUpdate(t *testing.T) {
}
}

func TestStoreReseedsAfterObservationGapExceedsWindowPeriod(t *testing.T) {
s, _ := newTestStore(t)
ctx := context.Background()
now := int64(1_000_000)
period := int64(quota.PeriodFor(quota.Window5Hour).Seconds())
reset := now + 9_000

_, err := s.UpdateAndGetBurnRates(ctx, []quota.Result{
makeResult("acct1", map[quota.WindowName]quota.Window{
quota.Window5Hour: {RemainingPct: 80, ResetAtUnix: reset},
}),
}, now)
if err != nil {
t.Fatalf("seed: %v", err)
}

rates, err := s.UpdateAndGetBurnRates(ctx, []quota.Result{
makeResult("acct1", map[quota.WindowName]quota.Window{
quota.Window5Hour: {RemainingPct: 70, ResetAtUnix: reset + period},
}),
}, now+period+60)
if err != nil {
t.Fatalf("resume: %v", err)
}
if _, ok := rates.Get(BurnRateKey{AccountKey: "acct1", Window: "5h"}); ok {
t.Fatal("stale observation produced burn rate after period-sized gap")
}

state, err := s.load()
if err != nil {
t.Fatalf("load: %v", err)
}
w := state.Accounts["acct1"].Windows["5h"]
if w.Samples != 1 || w.EWMARatePctPerS != 0 || w.LastRemainingPct != 70 {
t.Fatalf("reseeded state = %#v, want fresh sample at 70%%", w)
}
}

func TestStoreResetUnwrap(t *testing.T) {
s, _ := newTestStore(t)
ctx := context.Background()
Expand Down
29 changes: 29 additions & 0 deletions internal/output/tty_build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,35 @@ func TestBuildTTYModel_SingleProvider(t *testing.T) {
}
}

func TestBuildTTYModel_GenericDurationWindowUsesExactPeriod(t *testing.T) {
now := time.Unix(1_000, 0)
report := app.Report{
GeneratedAt: now,
Providers: []app.ProviderReport{{
ID: provider.Codex,
Name: "codex",
Results: []quota.Result{{
Status: quota.StatusOK,
Windows: map[quota.WindowName]quota.Window{
"1d": {RemainingPct: 75, ResetAtUnix: now.Unix() + 43_200},
},
}},
}},
}

model := BuildTTYModel(report, now)
if len(model.Sections) != 1 || len(model.Sections[0].WindowRows) != 1 {
t.Fatalf("model = %#v, want one 1d row", model)
}
row := model.Sections[0].WindowRows[0]
if got := strings.TrimSpace(stripANSI(row.Label)); got != "1d" {
t.Errorf("label = %q, want 1d", got)
}
if !strings.Contains(stripANSI(row.PaceDiff), "+25") {
t.Errorf("pace diff = %q, want +25", stripANSI(row.PaceDiff))
}
}

func TestBuildTTYModel_ErrorResult(t *testing.T) {
now := time.Unix(1000, 0)
report := app.Report{
Expand Down
81 changes: 43 additions & 38 deletions internal/provider/codex/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,25 @@ var (
codexPromoEndsAt = time.Date(2026, time.June, 1, 0, 0, 0, 0, time.UTC)
)

type usageWindow struct {
UsedPercent float64 `json:"used_percent"`
LimitWindowSeconds int64 `json:"limit_window_seconds"`
ResetAt any `json:"reset_at"`
}

type usageRateLimit struct {
PrimaryWindow *usageWindow `json:"primary_window"`
SecondaryWindow *usageWindow `json:"secondary_window"`
}

// parseUsage decodes a Codex usage API JSON body and returns a quota.Result.
func parseUsage(body []byte, email, accountID string) quota.Result {
var usage struct {
PlanType string `json:"plan_type"`
RateLimit *struct {
PrimaryWindow *struct {
UsedPercent float64 `json:"used_percent"`
ResetAt any `json:"reset_at"`
} `json:"primary_window"`
SecondaryWindow *struct {
UsedPercent float64 `json:"used_percent"`
ResetAt any `json:"reset_at"`
} `json:"secondary_window"`
} `json:"rate_limit"`
PlanType string `json:"plan_type"`
RateLimit *usageRateLimit `json:"rate_limit"`
AdditionalRateLimits []struct {
LimitName string `json:"limit_name"`
RateLimit *struct {
PrimaryWindow *struct {
UsedPercent float64 `json:"used_percent"`
ResetAt any `json:"reset_at"`
} `json:"primary_window"`
SecondaryWindow *struct {
UsedPercent float64 `json:"used_percent"`
ResetAt any `json:"reset_at"`
} `json:"secondary_window"`
} `json:"rate_limit"`
LimitName string `json:"limit_name"`
RateLimit *usageRateLimit `json:"rate_limit"`
} `json:"additional_rate_limits"`
}
if err := json.Unmarshal(body, &usage); err != nil {
Expand All @@ -52,30 +45,42 @@ func parseUsage(body []byte, email, accountID string) quota.Result {
return quota.Window{RemainingPct: pct, ResetAtUnix: parseNumericResetAt(resetAt)}
}

// Free accounts have only a weekly limit; their primary_window is a 7d window.
primaryWindowName := quota.Window5Hour
if usage.PlanType == "free" {
primaryWindowName = quota.Window7Day
}

windows := make(map[quota.WindowName]quota.Window)
if usage.RateLimit != nil {
if usage.RateLimit.PrimaryWindow != nil {
windows[primaryWindowName] = toWindow(usage.RateLimit.PrimaryWindow.UsedPercent, usage.RateLimit.PrimaryWindow.ResetAt)
addWindows := func(rateLimit *usageRateLimit, bucket string) error {
if rateLimit == nil {
return nil
}
if usage.RateLimit.SecondaryWindow != nil {
windows[quota.Window7Day] = toWindow(usage.RateLimit.SecondaryWindow.UsedPercent, usage.RateLimit.SecondaryWindow.ResetAt)
for _, entry := range []struct {
slot string
window *usageWindow
}{
{slot: "primary_window", window: rateLimit.PrimaryWindow},
{slot: "secondary_window", window: rateLimit.SecondaryWindow},
} {
if entry.window == nil {
continue
}
name, ok := quota.WindowNameForPeriod(entry.window.LimitWindowSeconds, bucket)
if !ok {
return fmt.Errorf("invalid %s limit_window_seconds", entry.slot)
}
if _, exists := windows[name]; exists {
return fmt.Errorf("conflicting rate limit window %q", name)
}
windows[name] = toWindow(entry.window.UsedPercent, entry.window.ResetAt)
}
return nil
}

if err := addWindows(usage.RateLimit, ""); err != nil {
return quota.ErrorResult("parse_error", fmt.Sprintf("parse: %v", err), 0)
}
for _, extra := range usage.AdditionalRateLimits {
if extra.LimitName == "" || extra.RateLimit == nil {
continue
}
if extra.RateLimit.PrimaryWindow != nil {
windows[quota.WindowName("5h:"+extra.LimitName)] = toWindow(extra.RateLimit.PrimaryWindow.UsedPercent, extra.RateLimit.PrimaryWindow.ResetAt)
}
if extra.RateLimit.SecondaryWindow != nil {
windows[quota.WindowName("7d:"+extra.LimitName)] = toWindow(extra.RateLimit.SecondaryWindow.UsedPercent, extra.RateLimit.SecondaryWindow.ResetAt)
if err := addWindows(extra.RateLimit, extra.LimitName); err != nil {
return quota.ErrorResult("parse_error", fmt.Sprintf("parse: %v", err), 0)
}
}

Expand Down
Loading