/** * Rolling median tokens_per_second meter โ€” SP-163, #84 part 1. * * Tracks local inference throughput samples and exposes a rolling median * estimate for hardware viability gating (wired in SP-164). * * Pure module with injectable sample store for testability. */ export interface ThroughputMeterConfig { readonly windowSize: number; readonly thresholdTps: number; } /** * Cold vs warm sample phase. * * - `warm` โ€” steady-state generation measured after model load. Only warm * samples count toward local viability. * - `cold` โ€” sample includes cold-start cost (model load, warmup). Cold * samples under-state steady-state TPS and never count toward viability; * they are reported separately as cold-start evidence. * * Untagged `recordSample()` calls default to `warm`: the routing pipeline * measures post-load generation duration. */ export type ThroughputSamplePhase = 'cold' | 'warm'; export interface ThroughputSample { readonly tokensPerSecond: number; readonly phase: ThroughputSamplePhase; } /** * Cold/warm breakdown of the rolling window. * * Formula: `warmMedianTps = median(tps where phase='warm')`, * `coldMedianTps = median(tps where phase='cold')`. * Viability (see {@link ThroughputMeter.isViable}): * `viable = warmSamples > 0 AND warmMedianTps >= threshold`. * When policy `requireWarmSamples` is true (default) and only cold samples * exist, viability fails closed (local reported unavailable). */ export interface ThroughputBreakdown { readonly warmMedianTps: number | null; readonly coldMedianTps: number | null; readonly warmSamples: number; readonly coldSamples: number; readonly classification: 'warm' | 'cold-only' | 'no-samples'; } /** Local viability policy for cold/warm TPS (SP-216, #116). */ export interface LocalViabilityPolicy { /** * Fail closed when only cold samples exist (quality-preserving default). * When false, cold-only windows are evaluated against the cold median. */ readonly requireWarmSamples: boolean; } export declare const DEFAULT_LOCAL_VIABILITY_POLICY: Readonly; /** Port for dependency injection in tests. */ export interface ThroughputSampleStore { push(tokensPerSecond: number): void; values(): readonly number[]; clear(): void; } /** * Optional tagged-store port. Stores implementing it retain the cold/warm * phase per sample; untagged stores are treated as all-warm. */ export interface TaggedThroughputSampleStore extends ThroughputSampleStore { pushSample(tokensPerSecond: number, phase: ThroughputSamplePhase): void; entries(): readonly ThroughputSample[]; } export interface ThroughputMeter { recordSample(tokens: number, durationMs: number, phase?: ThroughputSamplePhase): void; getMedianTps(phase?: ThroughputSamplePhase): number | null; isAboveThreshold(threshold?: number): boolean; getSampleCount(phase?: ThroughputSamplePhase): number; getBreakdown(): ThroughputBreakdown; isViable(policy?: LocalViabilityPolicy, threshold?: number): boolean; clear(): void; } /** Human-usable local viability threshold (~25 tok/s per routing-roadmap.md ยง3). */ export declare const DEFAULT_THROUGHPUT_THRESHOLD_TPS = 25; /** Rolling window over last N local inference samples (research default: 50). */ export declare const DEFAULT_THROUGHPUT_WINDOW_SIZE = 50; export declare const DEFAULT_THROUGHPUT_METER_CONFIG: Readonly; export declare class RollingThroughputSampleStore implements TaggedThroughputSampleStore { private readonly maxSize; private readonly samples; constructor(maxSize: number); push(tokensPerSecond: number): void; pushSample(tokensPerSecond: number, phase: ThroughputSamplePhase): void; values(): readonly number[]; entries(): readonly ThroughputSample[]; clear(): void; } export declare function computeTokensPerSecond(tokens: number, durationMs: number): number | null; export declare function median(values: readonly number[]): number | null; export declare function createThroughputMeter(config?: ThroughputMeterConfig, store?: ThroughputSampleStore): ThroughputMeter; //# sourceMappingURL=throughput-meter.d.ts.map