/**
 * Audio EOT (end-of-turn) detector base, the per-window inference stream, and
 * the transport interface that concrete cloud/local backends implement.
 *
 * Concrete implementations live in `agents/src/inference/eot/`.
 */
import type { AudioFrame } from '@livekit/rtc-node';
import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
import type { LanguageCode } from '../../language.js';
import type { EOTInferenceMetrics } from '../../metrics/base.js';
import { Future, Task } from '../../utils.js';
import type { ThresholdOptions, TurnDetectorModel } from './languages.js';
export declare const DEFAULT_SAMPLE_RATE = 16000;
export declare const MIN_SILENCE_DURATION_MS = 200;
/**
 * Options shared by the audio EOT stream and every transport.
 *
 * Cloud-only transport concerns (base URL, credentials, conn options)
 * live on a separate options class owned by the cloud transport.
 */
export interface BaseStreamingTurnDetectorOptions {
    sampleRate: number;
    thresholds: ThresholdOptions;
}
/**
 * Event emitted on each EOT prediction.
 */
export interface TurnDetectionEvent {
    type: 'eot_prediction';
    endOfTurnProbability: number;
    /** Wall-clock time when the prediction landed (milliseconds since epoch). */
    lastSpeakingTimeMs: number;
    /** Latest input-audio creation time → prediction receive time (ms). */
    detectionDelay?: number;
    /** Server-side model inference time (ms). */
    inferenceDuration?: number;
    /** How appropriate it is for the agent to backchannel at this pause.
     * `undefined` when the detector does not produce one (e.g. the local mini model). */
    backchannelProbability?: number;
}
/**
 * Sentinel value carried alongside flush requests. Signals a turn boundary
 * to the transport so it can clear its buffered audio.
 */
export interface FlushSentinel {
    readonly kind: 'flush';
    reason?: string;
}
export declare function isFlushSentinel(value: unknown): value is FlushSentinel;
/**
 * Transport adapter for `BaseStreamingTurnDetectorStream` — owns the I/O (WebSocket
 * session, in-process predict, etc.). The stream calls these methods
 * directly; transports report predictions back via
 * `stream._resolvePrediction(requestId, probability, ...)`.
 */
export interface StreamingTurnDetectionTransport {
    attach(stream: BaseStreamingTurnDetectorStream): void;
    run(): Promise<void>;
    runInference(requestId: string): void;
    pushFrame(frame: AudioFrame): Promise<void>;
    flush(sentinel: FlushSentinel): Promise<void>;
    detach(): void;
}
export type BaseStreamingTurnDetectorCallbacks = {
    metrics_collected: (metrics: EOTInferenceMetrics) => void;
};
declare const BaseStreamingTurnDetector_base: new () => TypedEmitter<BaseStreamingTurnDetectorCallbacks>;
/**
 * Abstract base for audio EOT detectors. Holds the threshold table and
 * provides `stream()` to create a per-turn FSM instance.
 *
 * Subclasses (`TurnDetector` in `inference/eot/detector.ts`) wire up
 * concrete transports.
 */
export declare abstract class BaseStreamingTurnDetector extends BaseStreamingTurnDetector_base {
    protected _opts: BaseStreamingTurnDetectorOptions;
    /**
     * Active streams the detector tracks for bulk teardown via `aclose()`.
     * `Set` rather than `WeakSet` because we need iteration; each stream
     * removes itself on its own `aclose` (see `BaseStreamingTurnDetectorStream.aclose`)
     * so the strong refs are released without requiring the caller to call
     * `detector.aclose()`.
     */
    protected _streams: Set<BaseStreamingTurnDetectorStream>;
    constructor(opts: BaseStreamingTurnDetectorOptions);
    /** @internal Stream lifecycle hook — called by the stream itself on close. */
    _unregisterStream(stream: BaseStreamingTurnDetectorStream): void;
    abstract get model(): TurnDetectorModel;
    get provider(): string;
    /** Most-recent materialized threshold map (after any cloud→local fallback
     * rescale or server-default adoption). */
    get thresholds(): Readonly<Record<string, number>>;
    /** Threshold below which the detector treats the prediction as "unlikely
     * to be end-of-turn". Returns `undefined` when the language isn't covered. */
    unlikelyThreshold(language: LanguageCode | undefined): Promise<number | undefined>;
    /** Threshold above which a pause is a backchannel opportunity, or `undefined`
     * when backchannel is disabled (server sent none, or the local mini model). */
    backchannelThreshold(language: LanguageCode | undefined): Promise<number | undefined>;
    supportsLanguage(language: LanguageCode | undefined): Promise<boolean>;
    abstract stream(): BaseStreamingTurnDetectorStream;
    aclose(): Promise<void>;
}
/**
 * Per-window inference stream. A thin transport-facing surface: per-request
 * state is one `(requestId, requestFut)` pair.
 *
 * - `predict()` starts a request and returns its future, superseding any
 *   previous request.
 * - the transport's single prediction completes the request by resolving the
 *   future via `_resolvePrediction`.
 * - `cancelInference()` / `flush(reason)` close a pending request, resolving
 *   its future with a default event so waiters never hang.
 *
 * All policy (when to start a request, await timeout, turn commits) lives in
 * `AudioRecognition`.
 */
export declare class SwapAbortError extends Error {
    constructor();
}
export declare class BaseStreamingTurnDetectorStream {
    protected _detector: BaseStreamingTurnDetector;
    protected _opts: BaseStreamingTurnDetectorOptions;
    protected _transport: StreamingTurnDetectionTransport;
    private _audioInputSampleRate;
    private _audioInputNumChannels;
    private _audioResampler;
    private _audioChannel;
    /** Id of the in-flight inference request, or `undefined` when idle. */
    protected _requestId: string | undefined;
    /** Future for the in-flight request; resolves to the prediction event (or
     * a default event when the request is cancelled / flushed). */
    protected _requestFut: Future<TurnDetectionEvent> | undefined;
    protected _mainTask: Task<void>;
    protected _logger: import("pino").Logger;
    /**
     * Aborted whenever the main loop needs to retry on a new transport (e.g.
     * fallback). The base FSM also aborts it from `aclose()` so idle
     * transports that are awaiting forever can be unstuck. Listeners check
     * `signal.aborted` and surface a sentinel rejection so the `_run` loop
     * can decide whether to continue or exit.
     */
    protected _swapController: AbortController;
    constructor(args: {
        detector: BaseStreamingTurnDetector;
        opts: BaseStreamingTurnDetectorOptions;
        transport: StreamingTurnDetectionTransport;
    });
    get model(): TurnDetectorModel;
    get provider(): string;
    /** @internal Shared threshold resolver — the cloud transport reads it to
     * adopt the server-sent defaults from `SessionCreated`. */
    get thresholdsOptions(): ThresholdOptions;
    unlikelyThreshold(language: LanguageCode | undefined): Promise<number | undefined>;
    backchannelThreshold(language: LanguageCode | undefined): Promise<number | undefined>;
    supportsLanguage(language: LanguageCode | undefined): Promise<boolean>;
    /** Start a new inference request and return its future, superseding any
     * previous request. */
    predict(): Future<TurnDetectionEvent>;
    /** Close the current inference request (new speech, turn boundary,
     * prediction timeout, mode change) and fall back if needed. */
    cancelInference(opts?: {
        timedOut?: boolean;
    }): void;
    flush(reason?: string): void;
    protected static _defaultEvent(probability: number): TurnDetectionEvent;
    pushAudio(frame: AudioFrame): void;
    endInput(): void;
    private _resampleAudioFrame;
    private _flushAudioResampler;
    private _resetAudioResampler;
    /**
     * Accept a prediction from a transport. A stale response (request id
     * mismatch) is ignored; otherwise the in-flight future resolves with the
     * full `TurnDetectionEvent` and the request completes.
     */
    _resolvePrediction(requestId: string, probability: number, opts?: {
        inferenceDuration?: number;
        detectionDelay?: number;
        backchannelProbability?: number;
    }): void;
    /**
     * Synchronously release this stream's registration on its owning detector,
     * so a replacement stream can be created before this one's async teardown
     * finishes. Base is a no-op; detectors that enforce single-stream ownership
     * override it. Idempotent.
     */
    detach(): void;
    aclose(): Promise<void>;
    /** True once `aclose()` has been called. The `_run` loop uses this to
     * distinguish swap-aborts (continue with new transport) from teardown
     * aborts (exit). */
    protected _closing: boolean;
    private _mainTaskBody;
    /**
     * Drain the shared audio channel into the current transport.
     *
     * The audio channel exposes a single `ReadableStream` (one underlying
     * `transform.readable`), so only one reader may hold its lock at a time.
     * When `signal` aborts (a transport being swapped out — e.g. cloud→local
     * fallback — fires it via `detach()`), we release the reader lock right
     * away: on a pending `read()` this rejects that read and frees the lock so
     * the swapped-in transport's `_drainAudioChannel` can re-acquire it.
     * Without this an orphaned drain would hold the lock forever and the next
     * `getReader()` would throw "ReadableStream is locked".
     */
    _drainAudioChannel(signal?: AbortSignal): Promise<void>;
    /** Default: hand control to the transport. Subclasses override for
     * cross-transport orchestration (e.g. cloud→local fallback). */
    protected _run(): Promise<void>;
    /**
     * Race `inner` against `_swapController.signal`. If the signal aborts
     * while `inner` is still pending, throw a `SwapAbortError` so the
     * subclass loop can decide whether to continue or exit. Resets the
     * controller after a swap-abort so subsequent races have a fresh signal.
     *
     * `aclose()` aborts during teardown — subclasses observe `_closing` to
     * exit cleanly instead of looping.
     */
    protected _raceWithSwap<T>(inner: Promise<T>): Promise<T>;
    /** @internal Wake up an idle transport so the main loop can pick up a
     * new one after fallback. Subclasses call this from their swap logic. */
    protected _signalSwap(): void;
    /** `predictEndOfTurn` timed out. Subclasses may override to react (e.g.
     * promote local on cloud timeout). */
    protected _onPredictTimeout(): void;
}
export {};
//# sourceMappingURL=base.d.ts.map