Skip to content

Generalize quota tracking with UsagePool support #3

@TheKrush

Description

@TheKrush

Summary

PromptFuel currently models quota usage as a small fixed set of named windows. That was enough for the original Claude/Codex quota surfaces, but it does not scale to newer plan mechanics such as usage credits, extra messages, included-vs-paid usage, monthly pools, or provider-specific tool pools.

Introduce a general UsagePool abstraction that can represent rolling windows, fixed windows, credit balances, included plan usage, paid overage usage, and approximate message/usage pools without hard-coding each provider's current quota shape into every layer.

Current behavior

Quota state is modeled around fixed fields such as fiveHour, sevenDay, and sevenDayOpus.

Several layers know about those specific windows directly, including merge behavior, reset planning, dashboard window keys, snapshot serialization, and status formatting.

This makes it difficult to support:

  • Claude usage credits.
  • Codex extra messages or add-on credits.
  • Monthly or weekly windows beyond the current hardcoded windows.
  • Per-tool pools such as Claude Code, Claude chat, Codex CLI, ChatGPT, or API usage.
  • Paid-overage-active states.
  • Included usage versus extra paid usage.
  • Approximate usage when exact limits are unavailable.

Desired behavior

PromptFuel should have a provider-neutral quota model where each quota/credit/allowance surface is represented as a UsagePool.

The existing 5h/7d/Opus windows should continue to work, but they should become specific pool instances rather than hardcoded first-class fields throughout the architecture.

The model should allow future support for warning users before they enter paid overage or start consuming extra credits.

Requirements

  • Add a provider-neutral UsagePool model or equivalent.

  • Support at least these pool concepts:

    • rolling window
    • fixed window
    • credit balance
    • allowance
  • Support at least these units:

    • percent
    • tokens
    • messages
    • requests
    • USD or credits
  • Represent whether a pool is included in the plan or extra/paid usage.

  • Represent overage policy:

    • block
    • spill to another pool
    • pay per use
  • Represent approximate usage when exact accounting is unavailable.

  • Represent derived states such as:

    • ok
    • approaching limit
    • at limit
    • overage active
    • unknown
  • Preserve existing Claude/Codex 5h/7d/Opus behavior.

  • Derive any legacy fields needed for compatibility instead of breaking current display/status behavior all at once.

  • Keep token/secret handling unchanged.

  • Do not persist raw prompts, raw provider responses, credentials, or tokens.

Non-goals / constraints

  • Do not implement a full paid-overage billing estimator in the first pass unless it naturally falls out of the model.
  • Do not remove existing dashboard/status behavior until equivalent pool-rendering behavior exists.
  • Do not hardcode only Claude usage credits and Codex extra messages; the abstraction should be provider-neutral.
  • Do not modify CHANGELOG unless this is part of an explicit release-prep task.
  • Do not change pricing math as part of this issue except where needed to represent overage policy metadata.
  • Do not create provider-specific one-off fields for every new quota type.

Suggested implementation

A possible minimal shape:

type PoolKind = 'rollingWindow' | 'fixedWindow' | 'creditBalance' | 'allowance';
type UsageUnit = 'percent' | 'tokens' | 'messages' | 'requests' | 'usd';
type UsageLevel = 'ok' | 'approaching' | 'atLimit' | 'overageActive' | 'unknown';

interface UsagePool {
  id: string;
  provider: string;
  toolSurface?: string;
  kind: PoolKind;
  window?: {
    durationSeconds?: number;
    resetsAtEpochSeconds?: number;
    rolling?: boolean;
  };
  unit: UsageUnit;
  used?: number;
  limit?: number;
  approximate?: boolean;
  includedInPlan: boolean;
  overage?: {
    policy: 'block' | 'spillToPool' | 'payPerUse';
    spillPoolId?: string;
    ratePerUnitUsd?: number;
  };
  balance?: {
    remaining: number;
    currency: 'usd' | 'credits';
    topUpEpochMs?: number;
  };
  sourceKind?: QuotaSourceKind;
  sourceUpdatedEpochMs?: number;
  level: UsageLevel;
}

Suggested phase split:

  1. Add UsagePool[] while deriving existing legacy fields from pools.
  2. Convert merge/reset/status/dashboard internals to operate per pool.
  3. Add pool rendering for existing 5h/7d/Opus windows.
  4. Add approaching-limit state and warning behavior.
  5. Add optional credit/overage support through settings or manual import.

Acceptance criteria

  • Existing Claude/Codex 5h/7d/Opus quota behavior remains unchanged from the user's perspective.
  • Existing tests pass after legacy fields are derived from pool data.
  • At least one test proves a new non-hardcoded window duration can be represented without adding a new top-level field.
  • At least one test proves included usage and paid/extra usage can be represented distinctly.
  • At least one test proves approaching, atLimit, and overageActive states can be derived without provider-specific branching.
  • No raw prompts, credentials, tokens, or raw provider responses are persisted.
  • The design leaves room for Claude usage credits and Codex extra messages without naming those as special one-off fields everywhere.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions