usage

package
v0.20.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package usage holds the account-level usage aggregator: a neutral Sample shape plus one in-memory Aggregator, the kb:adr/usage-no-source-interface seam for a second usage source down the line. Nothing here is Claude-Code-format vocabulary — internal/claudecode reads the status-line payload keys into its own neutral StatusAccount, and internal/server maps that into a Sample at the seam, so this package and the adapter stay mutually dependency-free.

Index

Constants

View Source
const DefaultModelScopedSource = defaultModelScopedSource

DefaultModelScopedSource exposes defaultModelScopedSource for internal/server's emptyUsageInfo placeholder — the one caller outside this package that needs the same default, so it reads this instead of keeping its own copy of the literal.

View Source
const DefaultSource = defaultSource

DefaultSource exposes defaultSource for internal/server's emptyUsageInfo placeholder — the one caller outside this package that needs the same default, so it reads this instead of keeping its own copy of the literal.

Variables

This section is empty.

Functions

This section is empty.

Types

type Aggregator

type Aggregator struct {
	// contains filtered or unexported fields
}

Aggregator holds the daemon's single, account-global usage reading in memory. It starts unknown at construction — no hydration from persisted usage_sample rows across a daemon restart (kb:adr/usage-no-hydration-across-restart). Record is its single writer (only the ingest worker goroutine calls it, so no second Record can interleave); mu guards current for Current()'s readers on other goroutines against that one writer.

func NewAggregator

func NewAggregator(cfg Config) *Aggregator

NewAggregator builds an Aggregator. Nothing here does I/O until Record is called.

func (*Aggregator) Current

func (a *Aggregator) Current() Snapshot

Current returns the aggregator's present snapshot — GET /api/state and the WS `hello` handshake's snapshot both read this.

func (*Aggregator) Record

func (a *Aggregator) Record(ctx context.Context, s Sample) error

Record applies one new sample: updates in-memory state, persists a usage_sample row, and invokes OnChange — but only when the bucket values or model changed since the last recorded sample (kb:adr/usage-sample-dedup-by-value). sampledAt advancing alone is never a change, which is why it's stamped here rather than accepted from the caller: the ~435 ms pair posts (kb:fact/status-posts-arrive-in-pairs) carry identical bucket/model values and must collapse to one broadcast and one row, not two.

type Bucket

type Bucket struct {
	UsedPct  float64
	ResetsAt time.Time
}

Bucket is one usage-limit readout (five-hour or seven-day). UsedPct and ResetsAt always travel together — a partial bucket is never modeled.

type Config

type Config struct {
	Store    *store.Store
	Logger   zerolog.Logger
	OnChange func(Snapshot)
}

Config wires an Aggregator. OnChange broadcasts the `usage` WS message; nil in tests.

type Model

type Model struct {
	ID          string
	DisplayName string
}

Model is the sampled status-line model, verbatim.

type ModelScoped

type ModelScoped struct {
	// contains filtered or unexported fields
}

ModelScoped holds the daemon's per-model weekly usage windows in memory — the second, independent usage holder kb:adr/usage-no-source-interface calls for, so Aggregator's single-writer assumption (aggregator.go's Record) stays true: each holder keeps exactly one writer (the status-line ingest path for Aggregator, musterd's own poller for this one).

func NewModelScoped

func NewModelScoped(cfg ModelScopedConfig) *ModelScoped

NewModelScoped builds a ModelScoped. Nothing here does I/O until Record or SetError is called.

func (*ModelScoped) Current

func (m *ModelScoped) Current() ModelSnapshot

Current returns ModelScoped's present snapshot — GET /api/state and the WS `hello` handshake's snapshot both read this.

func (*ModelScoped) Record

func (m *ModelScoped) Record(ctx context.Context, windows []ModelWindow) error

Record applies one successful fetch: dedups against the current list sorted by DisplayName — callers pass a non-nil, possibly empty, slice to distinguish "fetched, no windows" from "never fetched" — persists one usage_model_sample row per window before committing to memory when the list changed, and always clears a standing error — a success after a failure must broadcast even when the list itself is unchanged, since modelScopedError changed.

func (*ModelScoped) SetError

func (m *ModelScoped) SetError(kind string)

SetError records a failed poll (kb:adr/usage-model-window-polled-from-oauth-api): the last-good list and its timestamp are kept untouched. Logged at Warn the first time this error kind is seen in a row, Debug on repeats — a broadcast fires only on the Warn transition, since a repeat carries no new information the UI doesn't already have (modelScopedError is already that value).

type ModelScopedConfig

type ModelScopedConfig struct {
	Store    *store.Store
	Logger   zerolog.Logger
	OnChange func(ModelSnapshot)
}

ModelScopedConfig wires a ModelScoped. OnChange broadcasts the merged `usage` WS message; nil in tests.

type ModelSnapshot

type ModelSnapshot struct {
	Windows []ModelWindow
	At      *time.Time
	Error   *string
	Source  string
}

ModelSnapshot is ModelScoped's exposed state. Windows is nil until the first successful fetch; an empty-but-non-nil slice is a valid, distinct successful result (an account with no scoped windows) — At mirrors that nullability exactly. Error is nil except right after a failed poll — a success clears it even when the list itself didn't change. Source is never empty.

type ModelWindow

type ModelWindow struct {
	DisplayName string
	UsedPct     float64
	ResetsAt    time.Time
}

ModelWindow is one neutral per-model weekly usage window (kb:adr/usage-model-window-polled-from-oauth-api) — internal/claudecode's own UsageWindow is this seam's other half; internal/server maps between them at the seam, mirroring StatusAccount → Sample.

type Sample

type Sample struct {
	FiveHour  Bucket
	SevenDay  Bucket
	Model     Model
	SampledAt time.Time
	Source    string // "subscription" in v1
}

Sample is one neutral usage reading. SampledAt is stamped by Aggregator.Record, not by the caller (mirrors internal/store's own received_at/at stamping) — a caller-supplied value here would be ignored.

type Snapshot

type Snapshot struct {
	FiveHour  *Bucket
	SevenDay  *Bucket
	Model     *Model
	SampledAt *time.Time
	Source    string
}

Snapshot is the aggregator's exposed state. Every field is nil until the first post-boot sample lands (kb:adr/usage-no-hydration-across-restart: no hydration from persisted usage_sample rows across a daemon restart) — Source is the one field that is never null, matching the wire shape.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL