import type { AudioFrame, ParticipantKind } from '@livekit/rtc-node';
import { type Context, type Span } from '@opentelemetry/api';
import type { ReadableStream } from 'node:stream/web';
import { BaseStreamingTurnDetector, BaseStreamingTurnDetectorStream } from '../inference/eot/base.js';
import type { AdaptiveInterruptionDetector } from '../inference/interruption/interruption_detector.js';
import { type OverlappingSpeechEvent } from '../inference/interruption/types.js';
import type { LanguageCode } from '../language.js';
import { ChatContext } from '../llm/chat_context.js';
import { type StreamChannel } from '../stream/stream_channel.js';
import { type SpeechEvent } from '../stt/stt.js';
import { Task } from '../utils.js';
import { type VAD, type VADEvent } from '../vad.js';
import type { TurnDetectionMode } from './agent_session.js';
import { type EotPredictionEvent, type UserTurnExceededEvent, type _AgentBackchannelOpportunityEvent } from './events.js';
import type { STTNode } from './io.js';
import { type BaseEndpointing } from './turn_config/endpointing.js';
import type { UserTurnLimitOptions } from './turn_config/user_turn_limit.js';
export interface EndOfTurnInfo {
    /** The new transcript text from the user's speech. */
    newTranscript: string;
    /** Confidence score of the transcript (0-1). */
    transcriptConfidence: number;
    /** Delay from speech stop to final transcription in milliseconds. */
    transcriptionDelay: number | undefined;
    /** Delay from speech stop to end of utterance detection in milliseconds. */
    endOfUtteranceDelay: number | undefined;
    /** Timestamp when user started speaking (milliseconds since epoch). */
    startedSpeakingAt: number | undefined;
    /** Timestamp when user stopped speaking (milliseconds since epoch). */
    stoppedSpeakingAt: number | undefined;
    /**
     * When `true`, the agent activity commits the user turn to chat context but
     * skips generating the normal auto-reply. Mirrors python `_EndOfTurnInfo.skip_reply`.
     * Set, for example, when AMD has taken over the turn with a machine verdict and the
     * caller drives its own `generateReply` (e.g. leaving a voicemail).
     */
    skipReply?: boolean;
    /** The turn's speech overlapped agent speech and was classified a backchannel. */
    backchannelOverAgent?: boolean;
}
type EndOfTurnMetrics = {
    startedSpeakingAt: number | undefined;
    stoppedSpeakingAt: number | undefined;
    transcriptionDelay: number | undefined;
    endOfUtteranceDelay: number | undefined;
};
export declare function _computeEndOfTurnMetrics({ speechStartTime, lastSpeakingTime, lastFinalTranscriptTime, now, }: {
    speechStartTime: number | undefined;
    lastSpeakingTime: number | undefined;
    lastFinalTranscriptTime: number;
    now: number;
}): EndOfTurnMetrics;
export interface PreemptiveGenerationInfo {
    newTranscript: string;
    transcriptConfidence: number;
    /** Timestamp when user started speaking (milliseconds since epoch), if known. */
    startedSpeakingAt: number | undefined;
}
export interface RecognitionHooks {
    onInterruption: (ev: OverlappingSpeechEvent) => void;
    onBackchannelConfirmed: () => void;
    onStartOfSpeech: (ev: VADEvent) => void;
    onVADInferenceDone: (ev: VADEvent) => void;
    onEndOfSpeech: (ev?: VADEvent) => void;
    onInterimTranscript: (ev: SpeechEvent, speaking: boolean | undefined) => void;
    onFinalTranscript: (ev: SpeechEvent, speaking: boolean | undefined) => void;
    onTranscriptionTimeout: (speechDuration: number, turnStart: number) => void;
    onEndOfTurn: (info: EndOfTurnInfo) => Promise<boolean>;
    onEotPrediction: (ev: EotPredictionEvent) => void;
    onAgentBackchannelOpportunity: (ev: _AgentBackchannelOpportunityEvent) => void;
    onPreemptiveGeneration: (info: PreemptiveGenerationInfo) => void;
    onUserTurnExceeded: (ev: UserTurnExceededEvent) => void;
    retrieveChatCtx: () => ChatContext;
}
export declare class STTPipeline {
    static readonly PUMP_TASK_CANCEL_TIMEOUT = 5000;
    private sttNode;
    private _audioChannel;
    private _eventChannel;
    private _pumpTask;
    /** Wall-clock anchor for this stream, used with STT-relative timestamps. */
    inputStartedAt?: number;
    constructor(sttNode: STTNode);
    get audioChannel(): StreamChannel<AudioFrame, Error>;
    get eventChannel(): StreamChannel<SpeechEvent, Error>;
    private sttPump;
    close(): Promise<void>;
}
export interface _TurnDetector {
    /** The model name used by this turn detector. */
    readonly model: string;
    /** The provider name for this turn detector. */
    readonly provider: string;
    unlikelyThreshold: (language?: LanguageCode) => Promise<number | undefined>;
    supportsLanguage: (language?: LanguageCode) => Promise<boolean>;
    /**
     * Text-based detectors own their inference timeout behavior internally.
     */
    predictEndOfTurn(chatCtx: ChatContext, timeoutMs?: number): Promise<number>;
}
export interface AudioRecognitionOptions {
    /** Hooks for recognition events. */
    recognitionHooks: RecognitionHooks;
    /** Speech-to-text node. */
    stt?: STTNode;
    /** Voice activity detection. */
    vad?: VAD;
    /** Turn detector for end-of-turn prediction. Accepts text-based detectors
     * via `_TurnDetector` (e.g. plugins/livekit) or audio-based detectors via
     * `TurnDetector` (e.g. `inference.TurnDetector`). */
    turnDetector?: _TurnDetector | BaseStreamingTurnDetector;
    /** Turn detection mode. */
    turnDetectionMode?: TurnDetectionMode;
    interruptionDetection?: AdaptiveInterruptionDetector;
    /**
     * Backchannel boundary for adaptive interruption suppression, expressed in milliseconds.
     *
     * A single number applies to both the start and end of agent speech; a `[start, end]` tuple
     * configures them separately. `null` (or `undefined`) disables.
     */
    backchannelBoundary?: number | [number, number] | null;
    /** Endpointing delay strategy. */
    endpointing?: BaseEndpointing;
    /** User turn limit configuration. */
    userTurnLimit?: UserTurnLimitOptions;
    /** @deprecated Use endpointing instead. */
    minEndpointingDelay?: number;
    /** @deprecated Use endpointing instead. */
    maxEndpointingDelay?: number;
    /** Root span context for tracing. */
    rootSpanContext?: Context;
    /** STT model name for tracing */
    sttModel?: string;
    /** STT provider name for tracing */
    sttProvider?: string;
    /** Getter for linked participant for span attribution */
    getLinkedParticipant?: () => ParticipantLike | undefined;
    /** Predicate used to substitute silence for STT while still forwarding real audio elsewhere. */
    shouldDiscardAudioForStt?: (frame: AudioFrame) => boolean;
    /** User transcription timeout in milliseconds. `null` or `undefined` disables it. */
    transcriptionTimeout?: number | null;
}
/**
 * Minimal participant shape for span attribution.
 * Compatible with both `LocalParticipant` and `RemoteParticipant` from `@livekit/rtc-node`.
 */
export interface ParticipantLike {
    sid: string | undefined;
    identity: string;
    kind: ParticipantKind;
}
export declare class AudioRecognition {
    #private;
    private hooks;
    private stt?;
    private sttPipeline?;
    private vad?;
    private turnDetector?;
    private turnDetectorStream?;
    /**
     * Future for the in-flight audio-EOT inference request. Recognition owns the
     * request lifecycle: it starts a request on the VAD silence tick, holds the
     * future here, awaits it (with the model prediction timeout) in the eou bounce, and
     * clears it on turn boundaries / superseding speech.
     */
    private turnDetectorPredictionFut?;
    /**
     * True between a turn flush (commit / clearUserTurn) and the next VAD
     * start-of-speech. While set, a late stt final won't start a fresh request;
     * the eou bounce short-circuits via `onMissingEotPrediction`.
     */
    private turnDetectorFlushed;
    /** Warn once per recognition when the eou bounce runs after a flush. */
    private turnDetectorLatePredictionWarned;
    /**
     * The last `TurnDetectionEvent` we forwarded via `onEotPrediction`, kept
     * by reference to dedupe: both EOU triggers in a turn read the same
     * resolved prediction future, but the event should fire once per request.
     */
    private lastEmittedEotPrediction?;
    private warnedTurnDetectorPushFailure;
    private turnDetectionMode?;
    private endpointing;
    private userTurnLimit?;
    private lastLanguage?;
    private rootSpanContext?;
    private sttModel?;
    private sttProvider?;
    private getLinkedParticipant?;
    private deferredInputStream;
    private logger;
    private lastFinalTranscriptTime;
    private audioTranscript;
    private audioInterimTranscript;
    private audioPreflightTranscript;
    private finalTranscriptConfidence;
    private lastSpeakingTime;
    private speechStartTime;
    private userTurnStart;
    private userTurnCommitted;
    private speaking;
    private activeUserSpeakingSpan?;
    private vadSpeechStarted;
    private transcriptionTimeout?;
    private transcriptionTimeoutTimer?;
    private turnSpeechDuration;
    private turnTranscriptReceived;
    private sampleRate?;
    private userTurnSpan?;
    private userTurnTracker;
    private sttRequestIds;
    private vadInputStream;
    private sttInputStream;
    /**
     * Active subscriber writers fed from {@link subscribersBroadcast}. Each
     * {@link subscribeAudioStream} call appends one entry; entries are dropped
     * (and their stream closed) on {@link close}.
     *
     * The broadcast pattern replaces an earlier `tee()`-based approach where
     * the second branch grew without bound for sessions that never called
     * `subscribeAudioStream()` (every voice session that doesn't use AMD).
     * With this design no extra buffering happens until at least one
     * subscriber is registered.
     */
    private subscriberWriters;
    private silenceAudioTransform;
    private silenceAudioWriter;
    private sttOwnershipTransferred;
    private readonly sttLifecycleLock;
    private readonly vadLifecycleLock;
    private bounceEOUTask?;
    private commitUserTurnTask?;
    private sttForwardTask?;
    private vadTask?;
    private vadStream?;
    private sttConsumerTask?;
    private interruptionTask?;
    private interruptionDetection?;
    private ignoreUserTranscriptUntil?;
    private transcriptBuffer;
    private isInterruptionEnabled;
    private isAgentSpeaking;
    private agentSpeechStartedAt?;
    private interruptionDetected?;
    private overlapInCurrentTurn;
    private turnBackchannelOverAgent;
    private overlapOpen;
    private interruptionStreamChannel?;
    private interruptionSentinelWrite;
    private closed;
    private readonly closeWakeController;
    private backchannelBoundary?;
    private backchannelBoundaryTimer?;
    /** Callback invoked when the backchannel boundary timer expires naturally. */
    backchannelBoundaryCallback?: () => void;
    constructor(opts: AudioRecognitionOptions);
    /**
     * Current transcript of the user's speech, including interim transcript if available.
     */
    get currentTranscript(): string;
    /** @internal */
    get inputStartedAt(): number | undefined;
    /** @internal */
    get endOfTurnTask(): Task<void> | undefined;
    /** @internal */
    get endpointingOverlapping(): boolean;
    /** @internal */
    get isClosed(): boolean;
    /** @internal */
    updateOptions(options: {
        endpointing?: BaseEndpointing;
        turnDetection?: TurnDetectionMode | null;
    }): void;
    /**
     * Swap the active turn detector at runtime. When an `BaseStreamingTurnDetector`
     * is provided, opens a per-turn FSM stream after retiring the prior one.
     *
     * When `stream` is provided it is adopted as-is (handoff reuse) instead of
     * opening a fresh stream on `detector`; the live transport stream — and its
     * per-session cloud→local fallback state — survives the handoff.
     */
    updateTurnDetector(detector: _TurnDetector | BaseStreamingTurnDetector | undefined, options?: {
        stream?: BaseStreamingTurnDetectorStream;
    }): void;
    /**
     * Detach the turn detector stream for handoff to another AudioRecognition.
     *
     * Returns the live stream (transport run loop intact) without closing it.
     * The caller passes it to the new AudioRecognition via
     * `start({ turnDetectorStream })`. The stream stays attached to its
     * detector, retaining the detector's single-stream slot, so the new
     * AudioRecognition must adopt it rather than open a second stream.
     */
    detachTurnDetector(): BaseStreamingTurnDetectorStream | undefined;
    /**
     * The audio EOT detector needs a wider silence window than typical VAD
     * defaults. Rather than mutate the VAD's knob, require the caller to
     * configure it: raise if the bound VAD exposes `minSilenceDuration` and it
     * is below the floor. VADs that don't expose the knob are left untouched.
     */
    checkVadSilenceRequirement(detector?: _TurnDetector | BaseStreamingTurnDetector | undefined, vad?: VAD | undefined): void;
    private updateLastLanguage;
    updateStt(stt: STTNode | undefined, options?: {
        model?: string;
        provider?: string;
        resetContext?: boolean;
    }): Promise<void>;
    updateVad(vad: VAD | undefined): Promise<void>;
    start(options?: {
        sttPipeline?: STTPipeline;
        turnDetectorStream?: BaseStreamingTurnDetectorStream;
    }): Promise<void>;
    disableInterruptionDetection(): Promise<void>;
    /**
     * Whether the backchannel boundary timer is currently running.
     */
    get backchannelBoundaryActive(): boolean;
    /**
     * Fires when the backchannel boundary timer expires naturally. Drops the timer handle and
     * invokes the registered callback exactly once.
     */
    private onBackchannelBoundaryDone;
    /**
     * Cancel any pending backchannel boundary timer and clear the registered callback.
     */
    cancelBackchannelBoundary(): void;
    /**
     * Mark the start of active agent speech.
     *
     * This lifecycle follows audible playout, not the generation. Resuming paused playout starts
     * a new active-speech interval.
     */
    onStartOfAgentSpeech(startedAt: number): Promise<void>;
    /**
     * Mark the end of active agent speech.
     *
     * This can occur while the generation remains active, such as when playout is paused.
     */
    onEndOfAgentSpeech(ignoreUserTranscriptUntil: number): Promise<void>;
    /** Start interruption inference when agent is speaking and overlap speech starts. */
    onStartOfOverlapSpeech(speechDuration: number, startedAt: number, userSpeakingSpan?: Span): Promise<void>;
    private startOverlapInference;
    /** End interruption inference when overlap speech ends. */
    onEndOfOverlapSpeech(endedAt: number, userSpeakingSpan?: Span, agentEnded?: boolean): Promise<boolean | undefined>;
    private closeOverlap;
    /**
     * Flush held transcripts. When `force` is true, all buffered events are emitted during
     * interruption-detector teardown because ignore-window gating can no longer be trusted.
     * Otherwise, only transcripts whose end time is after `ignoreUserTranscriptUntil - cooldown`
     * are emitted. Events without timestamps are treated as the next valid event.
     */
    private flushHeldTranscripts;
    private resetInterruptionDetection;
    private withinIgnoreWindow;
    private shouldHoldSttEvent;
    private trySendInterruptionSentinel;
    private ensureUserTurnSpan;
    private userTurnContext;
    private onSTTEvent;
    private onOverlapSpeechEvent;
    private onMissingEotPrediction;
    private runEOUDetection;
    waitForEndOfTurnTask(): Promise<void>;
    private checkUserTurnLimit;
    private startSttTasks;
    private stopSttTasks;
    detachSttPipeline(): Promise<STTPipeline | undefined>;
    private forwardInputAudioToStt;
    private consumeSttEvents;
    private createVadTask;
    private createInterruptionTask;
    setInputAudioStream(audioStream: ReadableStream<AudioFrame>): void;
    detachInputAudioStream(): void;
    /**
     * Returns an independent ReadableStream of input audio frames. Each call
     * returns a fresh branch fed by the broadcast transform inserted into the
     * deferred input pipeline — frames written after the subscription point
     * will be delivered, but earlier frames are not replayed.
     *
     * Used by AMD when its private STT needs the same participant audio that
     * the pipeline is processing without consuming it (see python AMD
     * detector pushing audio into `_AMDClassifier.push_audio`).
     */
    subscribeAudioStream(): ReadableStream<AudioFrame>;
    clearUserTurn(): void;
    /**
     * Reset the VAD by restarting its task. This is needed when STT sends a premature
     * end-of-turn signal while the user is still speaking, so VAD can detect new speech
     * and trigger interruptions correctly.
     */
    private resetVad;
    private resetVadTask;
    private startVadTask;
    commitUserTurn(audioDetached: boolean): void;
    close(): Promise<void>;
    private cancelTranscriptionTimeout;
    private resetTranscriptionTimeout;
    private markTurnTranscribed;
    private armTranscriptionTimeout;
    private onTranscriptionTimeout;
    private _endUserTurnSpan;
    private get vadBaseTurnDetection();
}
export {};
//# sourceMappingURL=audio_recognition.d.ts.map