import { type UsageHeadroom, type UsageSnapshot, type UsageInfo } from './accounting/usage.js';
import type { AgentId } from './types.js';
/**
 * Default schedule between successful (or attempted) live usage fetches for one
 * account. Floor and ceiling of the delay helper are pinned to this so the
 * daemon does not poll faster than 5 minutes even under high burn, and does not
 * let an idle account rot longer than 5 minutes between attempts.
 */
export declare const REFRESH_INTERVAL_MS: number;
/** @deprecated alias — use {@link REFRESH_INTERVAL_MS}. Kept for test imports. */
export declare const REFRESH_MIN_MS: number;
/** @deprecated alias — use {@link REFRESH_INTERVAL_MS}. Kept for test imports. */
export declare const REFRESH_MAX_MS: number;
/** Burn-rate divisor retained for the pure delay helper / tests; with min=max
 * the divisor does not change the scheduled interval. */
export declare const REFRESH_BURN_DIVISOR = 4;
/** At most this many live fetches per account per rolling hour (5m cadence ⇒ 12). */
export declare const HOURLY_CALL_CAP = 12;
/** How often the daemon wakes to *consider* a refresh pass (due accounts only). */
export declare const USAGE_REFRESH_TICK_MS: number;
/**
 * Minimum wall-clock spacing between two live usage fetches to ONE network
 * provider, across all of its accounts. This is the pacing primitive: refreshes
 * are issued round-robin (stalest account first) no faster than one per spacing,
 * so aggregate endpoint load is a smooth, fixed rate — never the synchronized
 * burst-then-stall a plain rolling-hour cap produces when every account falls
 * due on the same tick. Set to two daemon ticks so the floor-based pacing lands
 * on exact tick boundaries (no drift): one refresh every other tick ⇒ 30/hr.
 */
export declare const PROVIDER_MIN_REFRESH_SPACING_MS: number;
/**
 * Aggregate live fetches this daemon may spend on ONE network provider's usage
 * endpoint per rolling hour, across ALL of that provider's local accounts —
 * derived from {@link PROVIDER_MIN_REFRESH_SPACING_MS} so the two are always
 * consistent (HOUR / 120s = 30).
 *
 * The per-account {@link HOURLY_CALL_CAP} alone scales linearly with account
 * count — 8 Claude accounts × 12/hr = ~96 usage calls/hr from one box — and
 * Anthropic's `/api/oauth/usage` rate-limits around ~100/hr (see the
 * `usage-backoff.ts` header). That tripped the endpoint into per-account 429s
 * with Retry-After penalties up to an hour: measured live on `zion`, 7 of 8
 * Claude accounts sat parked, never refreshed inside their 5h window, so
 * `agents view` showed `S: unavailable` and balanced routing read stale/absent
 * usage. It got WORSE with every account added.
 *
 * 30/hr is a fixed rate that does NOT grow with account count, and leaves ample
 * headroom under the ~100/hr ceiling for the auth probe (same endpoint, ~3/hr
 * per account, RUSH-2998) and foreground `agents view` bursts. Because refreshes
 * are paced round-robin (stalest first), each account's worst-case proactive
 * cadence is bounded at N × spacing (8 accounts ⇒ 16 min; 16 ⇒ 32 min) — kept
 * deliberately under the {@link USAGE_STALE_REFUSAL_MAX_AGE_MS} routing window so
 * a budget-paced account never reads as "genuinely stale". A slightly
 * older-but-present reading beats a 45-minute 429 park. Network providers only;
 * grok/codex read local logs and have no rate-limited endpoint.
 */
export declare const PROVIDER_HOURLY_BUDGET: number;
/**
 * Most refreshes a single tick may catch up after the daemon has been idle/down
 * (elapsed ≫ spacing). Without this clamp a long gap would grant many tokens at
 * once and re-synchronize every account into the very burst the spacing exists
 * to prevent. A small catch-up keeps the load smooth even after a restart.
 */
export declare const PROVIDER_CATCHUP_MAX = 2;
/**
 * A usage row this recently captured (by the free statusline ingest of a live
 * `agents run`, or any writer) is already fresh — do not spend an API call to
 * re-refresh it. Actively-used accounts stay current at zero endpoint cost, so
 * the proactive budget is reserved for genuinely idle accounts.
 */
export declare const STATUSLINE_FRESH_MS: number;
/** Consecutive failed live reads before one broken account is quarantined. */
export declare const FAILURE_QUARANTINE_THRESHOLD = 3;
/** A chronic offender waits this long while healthy siblings keep their cadence. */
export declare const FAILURE_QUARANTINE_MS: number;
/**
 * One account's refresh state + published headroom. `sessionUsedPercent` /
 * `capturedAt` are the prior sample the NEXT tick projects the burn rate from;
 * `minutesToLimit` / `status` are what the routing hot path reads.
 */
export interface HeadroomEntry {
    status: UsageHeadroom['status'];
    minutesToLimit: number | null;
    /** The session window's usedPercent in the last snapshot (the prev sample). */
    sessionUsedPercent: number | null;
    /** Epoch ms the last snapshot was captured. */
    capturedAt: number | null;
    /** Epoch ms this account is next due for a live refresh. */
    nextRefreshAt: number;
    /** Epoch ms of recent live fetches, for the rolling-hour cap. */
    callTimestamps: number[];
    /** Epoch ms this entry was written. */
    computedAt: number;
    /** Consecutive live-fetch misses; absent on entries written before this field. */
    consecutiveFailures?: number;
}
export declare function setHeadroomCachePathForTest(cachePath: string | null): string | null;
/** Read the whole headroom cache (best-effort; missing/corrupt → empty map). */
export declare function readHeadroomCache(): Record<string, HeadroomEntry>;
/** Read one account's headroom entry, or null. */
export declare function readHeadroomEntry(usageKey: string): HeadroomEntry | null;
/** Merge entries into the cache (best-effort; preserves other accounts' rows). */
export declare function writeHeadroomEntries(entries: Record<string, HeadroomEntry>): void;
/**
 * Interval until the next refresh attempt, clamped to [minMs, maxMs].
 * Defaults pin both ends to {@link REFRESH_INTERVAL_MS} (5 minutes) so the
 * live daemon path is a fixed schedule. Tests may pass a wider range to
 * exercise burn-aware scheduling without changing production cadence.
 */
export declare function computeNextRefreshDelayMs(minutesToLimit: number | null, opts?: {
    minMs?: number;
    maxMs?: number;
    divisor?: number;
}): number;
/** Recent call timestamps trimmed to the trailing hour. */
export declare function pruneCallTimestamps(timestamps: number[], now: number, windowMs?: number): number[];
/**
 * Whether an account may be live-refreshed right now: it is due (past its
 * scheduled `nextRefreshAt`) AND under the rolling-hour call cap. Pure so the
 * cadence + cap arithmetic is unit-tested without a daemon or a network call.
 */
export declare function shouldRefreshAccount(entry: HeadroomEntry | null | undefined, now: number, opts?: {
    hourlyCap?: number;
    windowMs?: number;
}): boolean;
/**
 * Build the next headroom entry after a live refresh: project headroom from the
 * new snapshot against the prior sample, schedule the next refresh from the
 * projection, and record this call for the hourly cap.
 */
export declare function nextHeadroomEntry(prev: HeadroomEntry | null | undefined, snapshot: UsageSnapshot | null, now: number): HeadroomEntry;
/**
 * Most-recent live-fetch time per network provider (the max call timestamp
 * across its accounts, 0 when none), which the smooth per-provider pacing spaces
 * the next refresh from. Non-network providers are omitted — they have no
 * rate-limited endpoint to pace.
 */
export declare function providerLastCall(accounts: LocalUsageAccount[], cache: Record<string, HeadroomEntry>): Map<AgentId, number>;
/**
 * How many live fetches the smooth pacing permits a provider THIS tick: one per
 * elapsed {@link PROVIDER_MIN_REFRESH_SPACING_MS} since its last fetch, clamped
 * to {@link PROVIDER_CATCHUP_MAX} so a long idle gap (or a cold provider with no
 * prior fetch) cannot re-burst the whole due set at once. At the daemon's 60 s
 * tick this yields at most one fetch every other tick in steady state (⇒ the
 * hourly budget), while a small fleet whose total demand fits under budget is
 * never throttled — the {@link PROVIDER_HOURLY_BUDGET} rolling cap is the only
 * gate that binds it.
 */
export declare function providerSpacingTokens(lastCallMs: number, now: number): number;
/** An account whose credentials live on the publisher host. */
export interface LocalUsageAccount {
    usageKey: string;
    agentId: AgentId;
    /**
     * Live-fetch this account's usage; the daemon passes the real network fetch.
     * `signal` (the daemon tick's deadline AbortSignal) bounds the provider fetch
     * so a hung refresh is aborted at deadlineMs, not just its own 5s timeout.
     */
    fetch: (signal?: AbortSignal) => Promise<UsageInfo>;
}
/**
 * Order a pass STALEST-FIRST so a scarce per-provider budget
 * ({@link PROVIDER_HOURLY_BUDGET}) is always spent on the accounts most in need
 * of a fresh reading, and no account is starved indefinitely.
 *
 *  - **Cold accounts** (never refreshed → no cache entry) are maximally stale
 *    and lead the pass. They rotate by `tick` so, when the budget can't cover
 *    them all in one tick, a different cold account leads each tick.
 *  - **Cached accounts** follow, oldest `capturedAt` first (a null capture time
 *    counts as maximally stale). As accounts refresh their `capturedAt` advances,
 *    so the next pass naturally rotates to whoever is now most out of date.
 */
export declare function orderUsageAccounts(accounts: LocalUsageAccount[], cache: Record<string, HeadroomEntry>, tick: number): LocalUsageAccount[];
/**
 * Aggregate live calls a network provider has already spent in the trailing hour,
 * summed across the accounts in this pass. Seeds the per-provider budget counter
 * so {@link PROVIDER_HOURLY_BUDGET} bounds the rolling-hour total, not just this
 * one tick. Non-network providers (grok/codex, local logs) are excluded — they
 * have no rate-limited endpoint to budget.
 */
export declare function providerRecentCalls(accounts: LocalUsageAccount[], cache: Record<string, HeadroomEntry>, now: number): Map<AgentId, number>;
/**
 * Enumerate the usage accounts whose credentials live on THIS host — one
 * per unique usage key, deduped to the most-recently-active version (the same
 * canonicalization `getUsageInfoByIdentity` uses). Each carries a closure that
 * live-fetches its usage. This is the daemon's `listAccounts`; because it only
 * ever lists local, signed-in accounts, each host is the sole writer for its own
 * accounts' caches. `runUsageRefreshTick` calls this on every host.
 */
export declare function buildLocalUsageAccounts(): Promise<LocalUsageAccount[]>;
/** Injectable side effects, so `runUsageRefresh` is drivable without the daemon. */
export interface UsageRefreshDeps {
    now?: number;
    /** Local-credential accounts to consider (one per unique usage key). */
    listAccounts: () => Promise<LocalUsageAccount[]>;
    /** Persist a fresh snapshot to the usage cache (writeClaudeUsageCache). */
    writeUsageCache: (usageKey: string, snapshot: UsageSnapshot) => void;
    /**
     * Epoch ms this provider — or, when `usageKey` is given, this specific
     * account — is backed off until; null when free (usageRateLimitedUntil).
     * Per-account scope (RUSH-3036): one throttled account must not park its
     * siblings, which previously starved every account after the first 429 in
     * this loop's fixed iteration order.
     */
    backoffUntil: (agentId: AgentId, usageKey?: string) => number | null;
    /**
     * The account's current usage row from the shared cache (the row the routing
     * hot path reads), or null when absent. Lets the refresher see the FREE
     * statusline ingest of a live `agents run` and, when that row is recent, skip a
     * redundant API refresh while still re-deriving headroom from it — instead of
     * spending scarce provider budget re-fetching an already-current account.
     */
    readCachedSnapshot?: (usageKey: string) => UsageSnapshot | null;
    /** Daemon tick deadline signal, forwarded to each account's provider fetch (PHNX-3608). */
    signal?: AbortSignal;
}
export interface UsageRefreshResult {
    refreshed: number;
    skippedNotDue: number;
    skippedBackoff: number;
    skippedCap: number;
    /** Skipped because the provider's rolling-hour budget was already spent. */
    skippedBudget: number;
    /** Skipped because a free statusline ingest already captured it recently. */
    skippedFresh: number;
    failed: number;
}
/**
 * One refresher tick: for each local account that is due, under its hourly cap,
 * and not backed off, live-fetch its usage, update the cache, and
 * reschedule from the new burn projection. Never throws — a single account's
 * failed fetch leaves its cache untouched and counts as `failed`.
 */
export declare function runUsageRefresh(deps: UsageRefreshDeps): Promise<UsageRefreshResult>;
