import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
import { LLM } from '../llm/llm.js';
import { STT } from '../stt/stt.js';
import type { AgentSession } from './agent_session.js';
import type { EndOfTurnInfo } from './audio_recognition.js';
export declare enum AMDCategory {
    HUMAN = "human",
    MACHINE_IVR = "machine-ivr",
    MACHINE_VM = "machine-vm",
    MACHINE_UNAVAILABLE = "machine-unavailable",
    UNCERTAIN = "uncertain"
}
export interface AMDPredictionEvent {
    type: 'amd_prediction';
    category: AMDCategory;
    transcript: string;
    reason: string;
    rawResponse: string;
    isMachine: boolean;
    /** Total speech duration captured before the verdict was settled (ms). */
    speechDurationMs: number;
    /** Time between the end of user speech and the verdict emission (ms). */
    delayMs: number;
}
export type AMDCallbacks = {
    amd_prediction: (event: AMDPredictionEvent) => void;
};
export interface AMDOptions {
    /**
     * LLM used to classify call greetings.
     * - `LLM` instance: used as-is (caller-owned; AMD will not close it).
     * - `string`: treated as a Cloud Inference model id (e.g. `'openai/gpt-4o-mini'`)
     *   and an inference LLM is constructed (AMD-owned).
     * - `undefined` (default): auto-select — if LiveKit Cloud inference credentials
     *   are available in the environment, uses `'google/gemini-3.1-flash-lite'` via
     *   the inference gateway; otherwise falls back to the session's own LLM.
     */
    llm?: LLM | string;
    /**
     * Dedicated STT used to transcribe call audio for AMD.
     * - `STT` instance: used as-is (caller-owned; AMD will not close it).
     * - `string`: treated as a Cloud Inference model id (e.g. `'cartesia/ink-whisper'`)
     *   and an inference STT is constructed (AMD-owned).
     * - `undefined` (default): auto-select — if LiveKit Cloud inference credentials
     *   are available in the environment, uses `'cartesia/ink-whisper'` via the
     *   inference gateway; otherwise reuses the session's existing STT transcripts.
     */
    stt?: STT | string;
    interruptOnMachine?: boolean;
    /** If no speech is heard within this window, settle as UNCERTAIN (not a machine, so no interrupt). */
    noSpeechTimeoutMs?: number;
    /** Hard ceiling for the entire detection. After this, settle with whatever evidence exists. */
    detectionTimeoutMs?: number;
    /** Speech longer than this is treated as machine-like (skips the short-greeting heuristic). */
    humanSpeechThresholdMs?: number;
    /** Silence after a short greeting before settling as HUMAN. */
    humanSilenceThresholdMs?: number;
    /** Silence after machine-like speech before opening the silence gate. */
    machineSilenceThresholdMs?: number;
    /**
     * If `true`, once any speech has been heard the `detectionTimeout` no longer
     * forces emission — AMD keeps waiting for post-speech silence and a positive
     * end-of-turn from the session's turn detector before emitting. Useful for
     * outbound voicemail flows where leaving a message early would overlap the
     * greeting. `noSpeechTimeout` (uncertain) still fires normally (no audio at
     * all means there is nothing to wait for). Defaults to `false`.
     * Mirrors python detector.py `wait_until_finished`.
     */
    waitUntilFinished?: boolean;
    /**
     * Fallback end-of-turn delay (ms). When the session turn detector never
     * commits a turn, this synthetic backstop, armed when speech ends, sets the
     * end-of-turn so a gated verdict can still emit. Defaults to the running
     * session activity's endpointing `maxDelay` (so the backstop tracks the real
     * turn detector), or {@link DEFAULT_MAX_ENDPOINTING_DELAY_MS} when no activity
     * is available. Mirrors python `max_endpointing_delay`.
     */
    maxEndpointingDelayMs?: number;
    /** Override the AMD classification system prompt. */
    prompt?: string;
    /**
     * Restrict AMD to a specific participant. Used to filter the
     * `waitForTrackPublication` gate (see python detector.py) and span
     * attribution. When unset, AMD binds to whichever participant the session
     * is linked to.
     */
    participantIdentity?: string;
    /**
     * If true, do not log a warning when the resolved LLM is not among the
     * bundled AMD-tested model strings. Has no effect on classification behavior.
     */
    suppressCompatibilityWarning?: boolean;
}
declare const AMD_base: new () => TypedEmitter<AMDCallbacks>;
/**
 * Answering Machine Detection.
 *
 * Mirrors Python's `_AMDClassifier` two-gate architecture:
 * a result is only emitted when both a **verdict** (from LLM or heuristic) and
 * a **silence gate** (from VAD or timeout) are satisfied.
 *
 * Emits `'amd_prediction'` once with the final {@link AMDPredictionEvent} when
 * a run settles (mirrors python `AMD(EventEmitter[Literal["amd_prediction"]])`).
 */
export declare class AMD extends AMD_base {
    private readonly session;
    private _log;
    private readonly llm;
    /** Dedicated STT for AMD-only transcription, or `undefined` if listening to session events. */
    private readonly stt;
    /** Whether AMD owns (and must close) the resolved LLM. False when the caller passed an instance. */
    private readonly llmOwned;
    /** Whether AMD owns (and must close) the resolved STT. */
    private readonly sttOwned;
    /**
     * Transcript source AMD consumes. `'amd_stt'` when AMD runs its own dedicated
     * STT (session transcripts are ignored), `'stt'` when it relies on the
     * session's STT transcripts. Mirrors python `_AMDClassifier._source`.
     */
    private readonly source;
    private readonly interruptOnMachine;
    private readonly noSpeechTimeoutMs;
    private readonly detectionTimeoutMs;
    private readonly humanSpeechThresholdMs;
    private readonly humanSilenceThresholdMs;
    private readonly machineSilenceThresholdMs;
    private readonly waitUntilFinished;
    private readonly maxEndpointingDelayMs;
    private readonly prompt;
    private readonly participantIdentity;
    private active;
    private listening;
    private settled;
    private transcriptParts;
    private verdictResult;
    /**
     * Two-gate emission (mirrors python classifier.py `_can_emit`): a verdict is
     * released only when `silenceReached` is true AND, for everything except a
     * confident human, `eotReached` is also true. Humans release on silence
     * alone so the agent can respond quickly.
     */
    private silenceReached;
    private eotReached;
    private speechStartedAt;
    private speechEndedAt;
    private detectGeneration;
    private extensionCount;
    private noSpeechTimer;
    private detectionTimer;
    private silenceTimer;
    private silenceTimerTrigger;
    /** Fallback end-of-turn backstop, armed when speech ends. */
    private eotTimer;
    private sttStream;
    private sttPumpTask;
    /**
     * Aborts pending {@link waitForTrackPublication} calls in
     * {@link gateListening}. Without this the room-event listener can
     * outlive the AMD instance if the participant track never publishes
     * before the run settles.
     */
    private trackGateAbort;
    /**
     * Tracks the in-flight LLM classification stream so {@link aclose} (and any
     * settled-after-aclose path) can abort it instead of leaving the network
     * call running. Mirrors python's `task.cancel()` on the outstanding
     * classification coroutine.
     */
    private currentLLMStream;
    private resolveRun;
    private rejectRun;
    private span;
    constructor(session: AgentSession, options?: AMDOptions);
    execute(): Promise<AMDPredictionEvent>;
    aclose(): Promise<void>;
    private resetState;
    private subscribe;
    /**
     * Arms the detection-timeout budget. Started in `execute()` as a backstop against a
     * never-published track, then re-armed in {@link gateListening} at track-up so the
     * effective budget runs from track-subscribe. Re-armable, hence the clear first.
     */
    private startDetectionTimer;
    /**
     * The no-speech timer measures how long we've been listening with nothing
     * coming back. It only makes sense after the participant track is actually
     * subscribed (otherwise we'd start counting silence against an audio source
     * that hasn't been wired up yet). Mirrors python detector.py's
     * `_setup` → `wait_for_track_publication` → `start_timers` flow.
     */
    private startNoSpeechTimer;
    private startListening;
    /**
     * Mirrors python detector.py `_run_stt`. When AMD owns its STT, it runs a
     * private pump that:
     *  1. Tees a fresh branch off the participant audio in `AudioRecognition`.
     *  2. Pushes frames into the STT stream.
     *  3. Forwards FINAL_TRANSCRIPTs into the classifier with `source = 'amd_stt'`.
     *
     * The session may not yet have an `AgentActivity` (and therefore no audio
     * stream) by the time AMD constructs its tasks, so we poll briefly until a
     * stream is available, bounded by the abort signal / settled state.
     */
    private startSTTPump;
    private runSTTPump;
    /**
     * If the session has a `_roomIO`, defer listening until the participant's
     * audio track is subscribed. For SIP participants, also wait until the call
     * is active so ringback and early media do not burn the no-speech budget.
     * Without `_roomIO`, fall back to listening immediately.
     */
    private gateListening;
    private cleanup;
    private clearTimer;
    /**
     * Ref: python classifier.py `_set_verdict` — stores the LLM/heuristic verdict.
     * Emission is deferred until the silence gate also opens.
     */
    private setVerdict;
    /**
     * Ref: python classifier.py `_try_emit_result` + `_can_emit` — releases a
     * verdict only when the silence gate is open AND, for everything except a
     * confident human, the end-of-turn gate is open too. Humans release on
     * silence alone so the agent can respond quickly.
     */
    private tryEmitResult;
    /** Ref: python classifier.py `_can_emit`. */
    private canEmit;
    private finish;
    /**
     * Hook invoked by {@link AgentActivity} when the user turn ends. Mirrors python
     * detector.py `_on_end_of_turn`: signals whether AMD is consuming this turn so
     * the activity should skip the normal reply pipeline.
     *
     * Returns `true` when AMD has settled on a machine verdict and the caller asked
     * us to take over via `interruptOnMachine`; the caller is expected to drive its
     * own `generateReply` (e.g. leaving a voicemail) and the auto-reply triggered by
     * user-turn completion would otherwise race with — and interrupt — it.
     *
     * @internal
     */
    onEndOfTurn(info: EndOfTurnInfo): boolean;
    /**
     * Ref: python classifier.py `_on_silence_reached` — the post-speech silence
     * window elapsed. Opens the silence gate and tries to emit a pending verdict.
     * Does NOT commit a fallback verdict and does NOT touch the end-of-turn gate.
     */
    private onSilenceReached;
    /**
     * Ref: python classifier.py `_on_timeout` — a timeout (detection budget,
     * no-speech, short greeting) fired. Commits a fallback verdict if none exists,
     * then tries to emit. This only decides *what* the verdict is; {@link canEmit}
     * decides *when* it is released. End-of-turn is forced here only when there is
     * nothing left to wait for: no speech was heard, or we are not waiting for the
     * greeting to finish (`waitUntilFinished` off). When `waitUntilFinished` is set
     * and speech was heard, the fallback is still committed but its release stays
     * gated on end-of-turn (the real signal or the backstop timer), so we don't cut
     * the greeting short with an `uncertain` result.
     *
     * Not gated by `listening`: detection_timeout must still fire when the call
     * never reaches listening (e.g. SIP never answered).
     */
    private onTimeout;
    /**
     * Ref: python classifier.py `_on_eot_reached` — the session turn detector
     * committed a turn (or the fallback backstop fired). Opens the end-of-turn
     * gate and tries to emit a pending verdict.
     */
    private onEotReached;
    private settleNoSpeech;
    private settleDetectionTimeout;
    /**
     * Ref: python classifier.py `on_user_speech_started` (called from
     * `AudioRecognition._on_vad_event` on VAD START_OF_SPEECH). A new speech
     * segment cancels the pending gates/timers and reopens both emission gates.
     *
     * @internal
     */
    onUserSpeechStarted(): void;
    /**
     * Ref: python classifier.py `on_user_speech_ended(silence_duration)` (called
     * from `AudioRecognition._on_vad_event` on VAD END_OF_SPEECH with
     * `ev.silence_duration`). `silenceDurationMs` is the silence already elapsed
     * when the VAD declared end-of-speech; the true speech-end time is therefore
     * `now - silenceDurationMs`, and every timer is shortened by it so it fires
     * `threshold` after speech actually ended. Mirrors python's
     * `speech_ended_at = time.time() - silence_duration` and
     * `max(0, threshold - silence_duration)`.
     *
     * @internal
     */
    onUserSpeechEnded(silenceDurationMs: number): void;
    /**
     * Ref: python classifier.py `push_text(text, source)` (called from
     * `AudioRecognition._on_transcript` with the session STT transcript, and from
     * AMD's dedicated STT with `source='amd_stt'`). Transcripts whose source is not
     * the one AMD is consuming are dropped — so when AMD runs its own STT the
     * session's transcripts are ignored, and vice versa.
     *
     * @internal
     */
    onTranscript(text: string, source?: 'stt' | 'amd_stt'): void;
    /**
     * Shared transcript ingestion: cancels the no-speech timer, refreshes the
     * silence-gate target if we were on the short-greeting path, and fires off
     * an LLM classification on the accumulated transcript.
     */
    private consumeTranscript;
    private readonly handleClose;
    /**
     * Schedules an LLM classification. Uses a generation counter so that if a
     * newer transcript arrives while an older classification is in-flight, the
     * stale result is discarded (mirrors Python's cancel-and-rerun pattern).
     */
    private scheduleLLMClassification;
    private classifyCurrentTranscript;
    /**
     * Mirrors python `_resolve_classifier`.
     * - `LLM` instance: caller-owned, used as-is.
     * - string: construct a Cloud Inference LLM (AMD-owned).
     * - `undefined`: fall back to `session.llm`.
     */
    private resolveLLM;
    /**
     * Mirrors python `_InferenceSTT(stt) if isinstance(stt, str) else stt`.
     * - `STT` instance: caller-owned.
     * - string: AMD-owned Cloud Inference STT.
     * - `undefined`: listen to session-level STT events.
     */
    private resolveSTT;
    private constructInferenceLLM;
    private constructInferenceSTT;
    private joinTranscript;
    private setSpanAttributes;
    /**
     * Builds two LLM tools — `save_prediction` (always) and `postpone_termination`
     * (until extensions exhausted) — and lets the LLM choose between committing
     * a verdict or extending the silence window.
     *
     * If the LLM returns plain JSON content instead of tool calls (e.g. mock
     * LLMs in tests, or providers that don't support `toolChoice='required'`),
     * we fall back to the pre-port JSON-content parsing path.
     */
    private detect;
    private computeSpeechDurationMs;
    private computeDelayMs;
    private parseDetection;
}
export {};
//# sourceMappingURL=amd.d.ts.map