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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
internal/web/dist/** linguist-generated=true -whitespace
assets/screenshots/** linguist-generated=true
tools/screenshots/fonts/*.ttf linguist-vendored=true
web/src/contracts.generated.ts text eol=lf linguist-generated=true
web/tests/projections.generated.json text eol=lf linguist-generated=true
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build test integration-test check fmt vet release-snapshot web-build web-test screenshots screenshots-check
.PHONY: build test integration-test check fmt vet release-snapshot web-build web-test screenshots screenshots-check web-contracts

# A tagged checkout gets the nearest semantic Git description. Repositories
# without a reachable tag leave this empty so Go's embedded VCS revision and
Expand Down Expand Up @@ -42,3 +42,6 @@ screenshots:
# No browser needed: compare current terminal cells, versions and image hashes.
screenshots-check:
node web/scripts/screenshots.mjs --check

web-contracts:
CODEXOMETER_WEB_GENERATE=1 go test ./internal/web -run '^TestBrowser(Contract|ProjectionFixtures)$$' -count=1
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3703,6 +3703,39 @@ OpenAI telemetry nor improves quota-estimation accuracy.

## Development

### Shared logic and browser contracts

Business rules belong in the Go core, not in a second renderer-specific
implementation. Backend quota windows use `codex.Meter.Key()` (limit ID, kind
and primary/secondary slot), never a mutable label. Session status and bounded
counter samples also share Go projections; each interface retains its own
selection, measurement baseline and display preferences.

`internal/quotaestimate` owns process-local API-EQ learning independently of
terminal geometry, colours and translations. `internal/quotagraph` supplies
coverage, regression, clipping and numeric Pace geometry to both interfaces.
Browser Pace geometry is tied to the quota observation's timestamp, not a
separately advancing browser clock; live reset timelines and countdown labels
still advance normally. Quota health, reset gauges and graph geometry share
elapsed-cycle calculations. Usage dimensions, historical warning thresholds,
breakdown ordering, units and relative bar scales are owned in Go, including
fractional totals and the distinction between missing and zero data.

Browser types, the local slash-command catalogue and numerical browser fixtures
are generated from the **browser-safe** Go contracts. Refresh them after a
contract or projector change:

```sh
make web-contracts
```

Ordinary Go tests check these generated files without rewriting them. Generate
from the safe projections, never raw session/client structs containing approval
capabilities or credentials. Chromium tests consume Go-produced numerical
fixtures; they do not implement another version of the calculations. No new
runtime dependency or framework is required. Terminal and SVG drawing remain
separate, as do browser-only security checks and transport-specific lifecycles.

Format, test, and vet the project:

```sh
Expand Down
2 changes: 1 addition & 1 deletion assets/screenshots/manifest.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

158 changes: 158 additions & 0 deletions internal/codex/session_projection_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package codex

import (
"reflect"
"testing"
"time"
)

func TestUsageWarningLevelIsSharedWithPeriodProjections(t *testing.T) {
for _, tc := range []struct {
known bool
value float64
want string
}{
{false, 100, "unknown"}, {true, -.25, "normal"}, {true, 0, "normal"},
{true, 79.99, "normal"}, {true, 80, "near"}, {true, 99.99, "near"},
{true, 100, "full"}, {true, 125, "full"},
} {
if got := UsageWarningLevel(tc.known, tc.value); got != tc.want {
t.Fatalf("known=%v value=%v level=%q want=%q", tc.known, tc.value, got, tc.want)
}
period := PlanUsagePeriod{}
if tc.known {
period.UsedBasisPoints = new(tc.value * 100)
}
for _, dimension := range UsageDimensions() {
if got := WindowUsageDistribution(period, dimension).Level; got != tc.want {
t.Fatalf("%s period level=%q want=%q", dimension, got, tc.want)
}
}
}
if d := DailyUsageDistribution(UsageBreakdownDay{Total: 125}, "surface"); d.Level != "" {
t.Fatal("daily relative units were classified as quota percentages", d)
}
}

func TestSessionStatusProjection(t *testing.T) {
for _, test := range []struct {
active, working bool
attention SessionAttention
code, want string
}{
{true, false, SessionAttentionNone, "", "IDLE"}, {true, true, SessionAttentionNone, "", "WORKING"},
{false, false, SessionAttentionNone, "", "INACTIVE"},
{true, true, SessionAttentionInput, "", "INPUT NEEDED"}, {true, true, SessionAttentionApproval, "", "APPROVAL NEEDED"},
{true, false, SessionAttentionCheck, "", "CHECK SESSION"}, {true, false, SessionAttentionComplete, "", "TURN COMPLETE"},
{true, false, SessionAttentionError, "serverOverloaded", "MODEL BUSY"},
{true, false, SessionAttentionError, "modelNotFound", "MODEL UNAVAILABLE"},
{true, false, SessionAttentionError, "policyCapacityUnavailable", "CAPACITY RESTRICTED"},
{true, false, SessionAttentionError, "future", "TURN FAILED"},
} {
if got := SessionStatus(test.active, test.working, test.attention, SessionContext{FailureCode: test.code}); got != test.want {
t.Fatalf("%+v: %q", test, got)
}
}
if SessionAttentionLabel(SessionAttentionNone, SessionContext{}) != "" {
t.Fatal("no attention invented an input request")
}
}

func TestMeterKeysIgnoreLabelsAndDistinguishEverySlot(t *testing.T) {
duration := int64(300)
label := "Shared label"
w := Window{WindowDurationMins: &duration}
q := Snapshot{RateLimitsByLimitID: map[string]RateLimitSnapshot{"a": {LimitName: &label, Primary: &w, Secondary: &w}, "b": {LimitName: &label, Primary: &w}}}
seen := map[MeterKey]bool{}
for _, meter := range q.Meters() {
if seen[meter.Key()] {
t.Fatal("identity collision", meter.Key())
}
seen[meter.Key()] = true
}
q.RateLimitsByLimitID["a"] = RateLimitSnapshot{Secondary: &w}
if key := q.Meters()[0].Key(); !seen[key] || key.Slot != 1 {
t.Fatal("secondary identity changed when primary disappeared", key)
}
}

func TestUsageDistributionFractionalUnitsAndMissingData(t *testing.T) {
day := UsageBreakdownDay{Total: .1, Groups: map[string]map[string]float64{"surface": {"cli": .05, "desktop": .05}}}
d := DailyUsageDistribution(day, "surface")
if d.Peak != .1 || d.Rows[0].Value/d.Peak != .5 || d.Rows[0].Name != "cli" {
t.Fatal("fractional scale/order changed", d)
}
missing := DailyUsageDistribution(UsageBreakdownDay{}, "surface")
empty := DailyUsageDistribution(UsageBreakdownDay{Groups: map[string]map[string]float64{"surface": {}}}, "surface")
if missing.KnownEmpty || !empty.KnownEmpty {
t.Fatal("missing was treated as zero")
}
used := 12500.0
period := PlanUsagePeriod{UsedBasisPoints: &used, Breakdowns: []PlanUsageBreakdown{
{Dimension: "thread_source", Rows: []PlanUsageValue{{Key: "agent", BasisPoints: 10000}}},
{Dimension: "thread_source", Rows: []PlanUsageValue{{Key: "agent", BasisPoints: 2500}}},
}}
p := WindowUsageDistribution(period, "feature")
if *p.Total != 125 || p.Peak != 125 || p.Rows[0].Value != 125 {
t.Fatal("window units or aggregation changed", p)
}
raw := &UsageReports{Daily: &DailyUsageReport{Days: []UsageBreakdownDay{day}}, Plan: &PlanUsageReport{Periods: []PlanUsagePeriod{period}}}
projected := ProjectUsageReports(raw)
if raw.Daily.Days[0].Views != nil || raw.Plan.Periods[0].Views != nil || !reflect.DeepEqual(projected.Daily.Days[0].Views["surface"], d) {
t.Fatal("projection mutated source or disagreed with renderer")
}
}

func TestUsageSamplesShareBoundsAndElapsedIntervals(t *testing.T) {
now := time.Unix(1800000000, 0)
var samples []UsageSample
for i := 0; i < 4; i++ {
samples = CaptureUsageSample(samples, int64((i+1)*10), int64(i*10), now, now.Add(time.Duration(i+1)*30*time.Second), 2)
}
if len(samples) != 2 || samples[0].At != now.Add(90*time.Second) || samples[1].Duration != 30*time.Second || samples[1].Tokens != 10 {
t.Fatal(samples)
}
samples = CaptureUsageSample(samples, 1, 100, now, now.Add(time.Minute), 2)
if samples[1].Tokens != 0 || samples[1].Duration != 0 {
t.Fatal("counter/time regressions became activity", samples)
}
if AppendUsageSample(samples, UsageSample{}, 0) != nil {
t.Fatal("unbounded zero limit")
}
}

func TestUsageDistributionPreservesSignedCorrectionsWithNonNegativeScale(t *testing.T) {
for _, tc := range []struct {
name string
total *float64
rows []PlanUsageValue
peak float64
}{
{"negative only", new(-25.0), []PlanUsageValue{{Key: "cli", BasisPoints: -25}}, 0},
{"negative without total", nil, []PlanUsageValue{{Key: "cli", BasisPoints: -25}}, 0},
{"mixed corrections", new(-25.0), []PlanUsageValue{{Key: "cli", BasisPoints: 100}, {Key: "unknown", BasisPoints: -125}}, 1},
{"positive total", new(100.0), []PlanUsageValue{{Key: "cli", BasisPoints: -25}}, 1},
} {
t.Run(tc.name, func(t *testing.T) {
period := PlanUsagePeriod{UsedBasisPoints: tc.total, Breakdowns: []PlanUsageBreakdown{{Dimension: "surface", Rows: tc.rows}}}
d := WindowUsageDistribution(period, "surface")
if d.Peak != tc.peak || d.KnownEmpty || len(d.Rows) != len(tc.rows) {
t.Fatalf("invalid correction scale: %+v", d)
}
if tc.total == nil && d.Total != nil || tc.total != nil && (d.Total == nil || *d.Total != *tc.total/100) {
t.Fatalf("signed or unknown total changed: %+v", d)
}
for _, raw := range tc.rows {
found := false
for _, row := range d.Rows {
if row.Name == raw.Key && row.Value == raw.BasisPoints/100 {
found = true
}
}
if !found {
t.Fatalf("signed row changed: %+v in %+v", raw, d.Rows)
}
}
})
}
}
30 changes: 30 additions & 0 deletions internal/codex/session_samples.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package codex

import "time"

// UsageSample is a process-local counter interval, never a billing record.
type UsageSample struct {
At time.Time `json:"at"`
Duration time.Duration `json:"-"`
Tokens int64 `json:"tokens"`
}

// Callers own their measurement baseline; both interfaces use the same delta,
// elapsed-interval and bounded-history rules without mixing different zeroes.
func CaptureUsageSample(samples []UsageSample, current, previous int64, startedAt, at time.Time, limit int) []UsageSample {
if len(samples) > 0 {
startedAt = samples[len(samples)-1].At
}
return AppendUsageSample(samples, UsageSample{At: at, Duration: max(at.Sub(startedAt), 0), Tokens: max(current-previous, 0)}, limit)
}

func AppendUsageSample(samples []UsageSample, sample UsageSample, limit int) []UsageSample {
if limit <= 0 {
return nil
}
if len(samples) >= limit {
copy(samples, samples[len(samples)-limit+1:])
samples = samples[:limit-1]
}
return append(samples, sample)
}
42 changes: 42 additions & 0 deletions internal/codex/session_state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package codex

// SessionAttentionLabel describes observed attention only. None is deliberately
// empty, not an inferred input request. Labels are canonical; adapters translate.
func SessionAttentionLabel(attention SessionAttention, context SessionContext) string {
switch attention {
case SessionAttentionInput:
return "INPUT NEEDED"
case SessionAttentionApproval:
return "APPROVAL NEEDED"
case SessionAttentionCheck:
return "CHECK SESSION"
case SessionAttentionError:
return SessionFailureLabel(context)
case SessionAttentionComplete:
return "TURN COMPLETE"
}
return ""
}

// SessionStatus is the common observed-state projection. Staleness, selected
// reviews and scheduled triggers are adapter overlays, not backend attention.
func SessionStatus(active, working bool, attention SessionAttention, context SessionContext) string {
if label := SessionAttentionLabel(attention, context); label != "" {
return label
}
if working {
return "WORKING"
}
if !active {
return "INACTIVE"
}
return "IDLE"
}

func SessionNeedsAttention(attention SessionAttention) bool {
return attention == SessionAttentionInput || attention == SessionAttentionApproval || attention == SessionAttentionCheck || attention == SessionAttentionError
}

func SessionRecentlyActive(active bool, attention SessionAttention) bool {
return active && attention != SessionAttentionCheck && attention != SessionAttentionError
}
24 changes: 23 additions & 1 deletion internal/codex/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ type Meter struct {
Window Window
Kind MeterKind
Details string
Slot int // Backend slot: primary=0, secondary=1; never a display label.
}

// MeterKey identifies a backend allowance independently of labels, consumption
// or reset timestamps. Features may add account/plan/cycle boundaries to it.
type MeterKey struct {
Limit string
Slot int
Kind MeterKind
}

func (m Meter) Key() MeterKey { return MeterKey{Limit: m.LimitID, Slot: m.Slot, Kind: m.Kind} }
func (k MeterKey) String() string { return fmt.Sprintf("%s/%d/%d", k.Limit, k.Kind, k.Slot) }

// DefinitionKey also distinguishes a changed allowance duration, but not the
// reset timestamp: a scheduled quota-recovery follow-up must survive a reset.
func (m Meter) DefinitionKey() string {
duration := int64(-1)
if m.Window.WindowDurationMins != nil {
duration = *m.Window.WindowDurationMins
}
return fmt.Sprintf("%s/%d", m.Key(), duration)
}

type MeterKind int
Expand Down Expand Up @@ -151,7 +173,7 @@ func (s Snapshot) Meters() []Meter {
meters = append(meters, Meter{Bucket: name, LimitID: key, Name: windowName(*bucket.Primary), Window: *bucket.Primary})
}
if bucket.Secondary != nil {
meters = append(meters, Meter{Bucket: name, LimitID: key, Name: windowName(*bucket.Secondary), Window: *bucket.Secondary})
meters = append(meters, Meter{Bucket: name, LimitID: key, Name: windowName(*bucket.Secondary), Window: *bucket.Secondary, Slot: 1})
}
if bucket.IndividualLimit != nil {
limit := bucket.IndividualLimit
Expand Down
Loading
Loading