import { type FactKind } from "./target-facts.js"; import type { CredentialId } from "./credential-id.js"; /** * MODULE CHARTER: Learned Stated Rate Limits Subsystem (rate-limits.ts) * * 1. Domain Boundary & Responsibilities: * - Parses and tracks rate limits explicitly STATED by upstream provider headers or response bodies. * - Records non-secret observed metrics into the shared `target-facts.ts` store. * - Serves as the rate limit measurement counterpart to `context-limits.ts`. * * 2. Measurement vs Condition Invariants: * - A stated rate limit is an immutable observation/measurement, not a transient health condition. * - Persisted across four discrete axes (`rpm`, `rpd`, `tpm`, `tpd`), each expiring independently on its TTL. * - Never cleared by successful calls, never cooled, and never triggers cost-blocking. * * 3. Confidence & Fallback Guarantees: * - Only records explicitly stated numerical ceilings with confidently identified axis AND period. * - Ambiguous error messages (e.g. "Rate limit exceeded" without numbers) learn NOTHING. * - Missing or unknown limits remain strictly `null`, never default to 0. */ /** Retained for callers and docs that name the measurement's staleness window (30 days). */ export declare const OBSERVED_RATE_LIMIT_TTL_MS: number; export type RateAxis = "requests" | "tokens"; export type RatePeriod = "minute" | "day"; /** A ceiling a backend stated about itself, with the axis and period it named. */ export type StatedRateLimit = { axis: RateAxis; period: RatePeriod; limit: number; /** * The statement named the ACCOUNT/KEY/ORGANIZATION as its subject ("this API key allows…", * "your organization's rate limit of…"). Only then may the fact widen from attempt scope (this * credential × this model) to credential scope (every model on this key) — scope comes from * evidence in the wording, never from counting how many models happened to trip. */ accountWording?: true; }; /** * Extract every stated rate ceiling from a response body, or null when none is stated. * * A body naming several ceilings (an RPM clause and a TPM clause side by side) yields EACH, one * per axis×period bucket; two readings of the SAME bucket collapse to the first stated. Returns * null — never an empty array and never a guess — when nothing qualifies. */ export declare function parseStatedRateLimit(body: string): StatedRateLimit[] | null; /** Cheap pre-filter for callers that want to skip parsing on bodies that cannot state a limit. */ export declare function looksLikeRateLimitError(body: string): boolean; /** `requests`+`day` → `rate-limit-rpd`, and so on for the four measurement kinds. */ export declare function rateLimitFactKind(limit: Pick): FactKind; /** The inverse of {@link rateLimitFactKind}; null for every kind that is not a rate measurement. */ export declare function rateLimitAxisOf(kind: FactKind): Pick | null; /** * Record ceilings a deployment stated about itself. * * Scope follows the evidence, never a heuristic: attempt scope (this credential × this model) * when the credential is known, deployment scope when it is not, and credential scope (every * model on this key) ONLY when the statement itself named the account/key/organization as its * subject. It never widens by counting sibling models — that inference is exactly what produced * false "your key is broken" verdicts elsewhere, and it is refused here too. */ export declare function recordObservedRateLimit(provider: string, credentialId: CredentialId | null, model: string, stated: StatedRateLimit | StatedRateLimit[], opts?: { path?: string; now?: number; }): void; /** * The learned limits covering this cell, MOST-SPECIFIC-FIRST (attempt → credential → deployment), * each labelled `learned`. Display-only today; a caller that wants to ACT on one must announce * that separately (spec M2). */ export declare function observedRateLimits(provider: string, credentialId: CredentialId | null, model: string, opts?: { path?: string; now?: number; }): Array<{ axis: RateAxis; period: RatePeriod; limit: number; basis: "learned"; until: number; }>; /** Flush pending observations. Called on shutdown, like the other write-behind stores. */ export declare function flushObservedRateLimits(opts?: { path?: string; }): void; /** Test seam: drop the in-memory store so a suite can point at a fresh path. */ export declare function resetObservedRateLimits(): void;