usage

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package usage exposes provider subscription allowances (Claude, Codex) read from a Redline service running on the same host, mapped onto a DevX-owned wire shape so Redline's model never leaks into DevX clients.

Index

Constants

View Source
const (
	// DashboardTimeout bounds a dashboard read when the caller's context carries
	// no deadline; the read is a DB-only query so it should never come close.
	DashboardTimeout = 5 * time.Second
	// RefreshTimeout bounds one provider refresh when the caller's context
	// carries no deadline. Redline allows 10s per upstream fetch.
	RefreshTimeout = 12 * time.Second
)
View Source
const DefaultBaseURL = "http://127.0.0.1:7436"

DefaultBaseURL is where Redline listens by default.

View Source
const DefaultPollInterval = 30 * time.Second

DefaultPollInterval matches the plan's usage.poll_interval default; Redline's dashboard read is DB-only so this is cheap. cmd/root.go and web.UsageOptionsFromGlobalConfig build their viper/option defaults from this so there is one source of truth for the interval.

View Source
const MinPollInterval = 5 * time.Second

MinPollInterval is the floor for usage.poll_interval: anything below this (including a misconfigured value that parses to a tiny duration, like a bare number of nanoseconds) is clamped up rather than hammering Redline.

View Source
const RefreshMinSpacing = 15 * time.Second

RefreshMinSpacing is the minimum time between two provider refreshes; extra requests reuse the cached usage.

Variables

View Source
var ErrTimeout = errors.New("redline did not respond in time")

ErrTimeout reports that Redline was reachable but did not answer within the call's deadline, which is a different diagnosis from it not running.

View Source
var ErrUnauthorized = errors.New("redline rejected the API token")

ErrUnauthorized reports that Redline rejected the API token.

View Source
var ErrUnreachable = errors.New("redline unreachable")

ErrUnreachable reports that the Redline service could not be contacted, which normally means it is not running on this host.

Functions

func DiscoverToken

func DiscoverToken(tokenFile string) (token string, path string, err error)

DiscoverToken resolves the Redline API token: the REDLINE_API_TOKEN environment variable wins, then an explicit tokenFile, then the platform default beside redline.yaml. It also returns the path the token came from (empty for the environment variable) so callers can name it in a "token not found at …" message.

Types

type Client

type Client struct {
	BaseURL    string
	Token      string
	HTTPClient *http.Client
}

Client talks to a Redline service over its loopback HTTP API. Prefer NewClient, which validates the base URL before any token is put on the wire.

func NewClient

func NewClient(baseURL, token string, allowRemote bool) (*Client, error)

NewClient validates baseURL and returns a Client for it. An empty baseURL means DefaultBaseURL. Non-loopback hosts are refused unless allowRemote is set, so a mistyped URL cannot ship the Redline bearer token elsewhere, and an allowed remote host must speak https so the token is never sent in the clear. The returned BaseURL keeps only the scheme and host.

func (*Client) Dashboard

func (c *Client) Dashboard(ctx context.Context) ([]byte, error)

Dashboard returns the raw body of Redline's GET /v1/dashboard. When ctx has no deadline, DashboardTimeout applies.

func (*Client) Refresh

func (c *Client) Refresh(ctx context.Context, providerID string) error

Refresh asks Redline to fetch live usage for one provider account id, for example "claude-main". When ctx has no deadline, RefreshTimeout applies.

type Poller

type Poller struct {
	Client   *Client
	Interval time.Duration
	// KeepStaleFor is how long the last good providers survive while Redline is
	// unavailable. Defaults to 15 minutes.
	KeepStaleFor time.Duration
	// Now defaults to time.Now; tests inject a clock.
	Now func() time.Time
	// OnChange is called whenever the marshalled payload differs from the
	// previous one.
	OnChange func(Usage)

	// Logger receives state transitions and refresh failures. A nil Logger
	// discards them, which is what a Bubble Tea TUI needs: stray writes to
	// stderr corrupt the display.
	Logger *log.Logger
	// contains filtered or unexported fields
}

Poller keeps a cached Usage snapshot fresh by polling Redline's dashboard. The zero value is usable except for Client, which must be set.

func (*Poller) Current

func (p *Poller) Current() Usage

Current returns the cached usage without contacting Redline.

func (*Poller) Poll

func (p *Poller) Poll(ctx context.Context) Usage

Poll performs a single dashboard read and stores the result. It is single flighted: concurrent callers queue so OnChange can never deliver a payload older than Current(). Only the owner of the Poller should call it; other callers want Current, which never blocks on Redline.

func (*Poller) Refresh

func (p *Poller) Refresh(ctx context.Context) (RefreshResult, error)

Refresh asks Redline to fetch live usage for every known provider and then re-reads the dashboard. It returns Throttled without contacting Redline when a refresh is already in flight or one ran less than RefreshMinSpacing ago.

The returned error joins the per-provider refresh failures; the dashboard re-read still happens and Usage is still populated, so a caller can surface the error while showing fresh-as-possible data.

ctx only aborts the caller's wait: the Redline calls and the cache write run on the poller's own context, so a disconnected browser can never blank the shared state for everybody else.

func (*Poller) Start

func (p *Poller) Start(ctx context.Context) <-chan struct{}

Start polls Redline immediately and then every Interval until ctx is done. It returns as soon as the background loop is running; the returned channel is closed when the loop has exited, so Shutdown can wait for an in-flight poll. ctx also becomes the poller's own lifecycle context, which is what Refresh and the loop use for their Redline calls.

Starting an already-started Poller panics: two loops over one cache is a programming error, not a runtime condition.

type Provider

type Provider struct {
	ID       string `json:"id"`
	Provider string `json:"provider"`
	Label    string `json:"label"`

	State ProviderState `json:"state"`
	// Error is populated only when State is ProviderError (no usable snapshot).
	// Redline's per-account error text can contain filesystem paths, so it is
	// deliberately dropped for the ok/stale cases where a usable snapshot exists
	// alongside it. Capped at 200 runes.
	Error      string    `json:"error,omitempty"`
	Source     string    `json:"source,omitempty"`
	ObservedAt time.Time `json:"observed_at"`
	// Primary is the account-scoped window shown without a click; nil when the
	// account exposes no account-scoped window at all. When non-nil its Key
	// always matches exactly one Windows entry (the labels differ: "5h"/"wk"
	// here versus the expanded-view label in Windows).
	Primary *Window  `json:"primary,omitempty"`
	Windows []Window `json:"windows"`
}

Provider is one Redline provider account (for example "claude-main").

type ProviderState

type ProviderState string

ProviderState is one provider account's state. It marshals as a plain JSON string.

const (
	// ProviderOK means the snapshot is present and fresh.
	ProviderOK ProviderState = "ok"
	// ProviderStale means Redline flagged the snapshot as older than its
	// max_snapshot_age.
	ProviderStale ProviderState = "stale"
	// ProviderError means there is no usable snapshot for this account.
	ProviderError ProviderState = "error"
)

type RefreshResult

type RefreshResult struct {
	// Usage is the state after the refresh, or the cached state when throttled.
	Usage Usage
	// Throttled is true when the call did not contact Redline because another
	// refresh is in flight or one ran less than RefreshMinSpacing ago.
	Throttled bool
	// RetryAfter is how long until a refresh would be accepted; only meaningful
	// when Throttled.
	RetryAfter time.Duration
}

RefreshResult reports the outcome of Refresh.

type State

type State string

State is the top-level usage state. It marshals as a plain JSON string.

const (
	// StateLoading is reported until the first poll completes.
	StateLoading State = "loading"
	// StateOK means the last Redline read succeeded.
	StateOK State = "ok"
	// StateUnavailable means Redline could not be read; Message says why.
	StateUnavailable State = "unavailable"
	// StateDisabled means provider usage is turned off by configuration.
	StateDisabled State = "disabled"
)

type Usage

type Usage struct {
	State State `json:"state"`
	// Message is human-readable text for the unavailable and disabled states.
	Message string `json:"message,omitempty"`
	// UpdatedAt is when DevX last read Redline successfully.
	UpdatedAt time.Time  `json:"updated_at"`
	Providers []Provider `json:"providers"`
}

Usage is the top-level payload DevX serves to its clients.

Values returned by Poller.Current, Poller.Poll and Poller.OnChange are copies the caller owns: Providers, Windows and Primary may be read, sorted or edited in place without affecting the poller's cache.

func Disabled

func Disabled() Usage

Disabled is the Usage served when provider usage is turned off, so the disabled wording lives here rather than in every consumer.

func MapDashboard

func MapDashboard(raw []byte, now time.Time) (Usage, error)

MapDashboard maps a Redline GET /v1/dashboard payload onto the DevX wire shape. now becomes Usage.UpdatedAt.

type Window

type Window struct {
	Key   string `json:"key"`
	Label string `json:"label"`
	Scope string `json:"scope"`
	// Remaining is the fraction of the allowance left, 0..1.
	Remaining     float64   `json:"remaining"`
	ResetsAt      time.Time `json:"resets_at"`
	PeriodSeconds int64     `json:"period_seconds,omitempty"`
	ResetInferred bool      `json:"reset_inferred,omitempty"`
}

Window is one allowance window (account- or model-scoped).

Jump to

Keyboard shortcuts

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