import type { AudioFrame } from '@livekit/rtc-node';
import { AsyncLocalStorage } from 'node:async_hooks';
import { ReadableStream } from 'node:stream/web';
import type { Logger } from 'pino';
import { type BaseStreamingTurnDetectorStream } from '../inference/eot/base.js';
import type { OverlappingSpeechEvent } from '../inference/interruption/types.js';
import { type ChatContext, ChatMessage, type Instructions } from '../llm/chat_context.js';
import { type GenerationCreatedEvent, type InputSpeechStartedEvent, type InputSpeechStoppedEvent, type InputTranscriptionCompleted, LLM, RealtimeModel, type RealtimeSession, type Tool, type ToolChoice, ToolContext, type ToolContextLike } from '../llm/index.js';
import { STT, type SpeechEvent } from '../stt/stt.js';
import { TTS } from '../tts/tts.js';
import { Future, Task } from '../utils.js';
import { VAD, type VADEvent } from '../vad.js';
import { Agent, type AgentUpdateOptions } from './agent.js';
import { type AgentSession, type ExpressiveOptions, type TurnDetectionMode } from './agent_session.js';
import { type EndOfTurnInfo, type PreemptiveGenerationInfo, type RecognitionHooks, type STTPipeline } from './audio_recognition.js';
import type { EotPredictionEvent, UserTurnExceededEvent, _AgentBackchannelOpportunityEvent } from './events.js';
import type { ToolOutput } from './generation.js';
import { type _AudioOut, type _TextOut } from './generation.js';
import { type InputDetails, SpeechHandle } from './speech_handle.js';
import { ToolExecutor } from './tool_executor.js';
import { type EndpointingOptions } from './turn_config/endpointing.js';
export declare const agentActivityStorage: AsyncLocalStorage<AgentActivity>;
export declare const onEnterStorage: AsyncLocalStorage<OnEnterData>;
/**
 * Whether two transcripts of the same utterance differ only in formatting (casing,
 * punctuation, whitespace), so a preemptive generation started from `first` may still be
 * reused once the finalized transcript is `second`.
 *
 * The CJK/Thai alternation emulates Python's `split_words(..., split_character=True)`, which
 * splits those scripts per character; the JS tokenizer does not expose that option. The shared
 * regex carries the `g` flag, which is safe here only because `String.prototype.match` resets
 * `lastIndex` before iterating — switching to `.exec()` or `.test()` would silently break it.
 *
 * Full-width CJK punctuation (e.g. `。`) is not stripped by either implementation. This is a
 * shared upstream limitation, not a JS-only defect — do not "fix" it on the JS side alone.
 *
 * `toLowerCase()` is a weaker fold than Python's `casefold()`. Do not replace it with
 * `casefold()` or add Unicode NFKC normalization: differential execution over 18 inputs found
 * 15 agree and 3 diverge (`STRASSE`/`straße`, `ΟΔΟΣ`/`οδοσ`, `ﬁle`/`file`), and in every
 * divergent case JS regenerates where Python reuses — never the reverse. The asymmetry is
 * therefore conservative in the safe direction: JS may do redundant work, but it never reuses a
 * generation that Python would have invalidated. NFKC would over-normalize full-width forms and
 * introduce exactly the unsafe asymmetry that does not currently exist.
 *
 * @internal Exported for testing only; `agent_activity.ts` is not part of the public API surface.
 */
export declare function transcriptsEquivalent(first: string, second: string | undefined): boolean;
interface OnEnterData {
    session: AgentSession;
    agent: Agent;
}
export interface ReusableResources {
    sttPipeline?: STTPipeline;
    rtSession?: RealtimeSession;
    turnDetectorStream?: BaseStreamingTurnDetectorStream;
}
export declare class SchedulingPausedError extends Error {
    constructor();
}
export declare function isSchedulingPausedError(error: unknown): error is SchedulingPausedError;
export declare function cleanupReusableResources(resources: ReusableResources, logger: Logger): Promise<void>;
export interface ForwardOutput {
    played: 'full' | 'partial' | 'skipped';
    textOut: _TextOut | null;
    synchronizedTranscript?: string;
    audioOut: _AudioOut | null;
    playbackPositionInS: number;
}
export declare class AgentActivity implements RecognitionHooks {
    agent: Agent;
    agentSession: AgentSession;
    private started;
    private closed;
    private audioRecognition?;
    private realtimeSession?;
    private realtimeSpans?;
    private turnDetectionMode?;
    private rtOverlappingSpeechEnabled;
    private logger;
    private _schedulingPaused;
    private newTurnsBlocked;
    private _authorizationPaused;
    private _drainBlockedTasks;
    private _currentSpeech?;
    private activeAgentStateLease?;
    private speechQueue;
    private userSilenceEvent;
    private q_updated;
    private speechTasks;
    private _backgroundSpeeches;
    private pendingAutoToolReplyFut?;
    private lock;
    private inlineTaskLock;
    private audioStream;
    private audioStreamId?;
    private toolChoice;
    private _preemptiveGeneration?;
    private _preemptiveGenerationCount;
    private _toolsetsSetup;
    private _suppressToolsetConfigUpdate;
    private readonly closeAbort;
    private interruptionDetector?;
    private isInterruptionDetectionEnabled;
    private interruptionDetected;
    private isInterruptionByAudioActivityEnabled;
    private isDefaultInterruptionByAudioActivityEnabled;
    /**
     * Validated turn detection for this activity. Equals `this.turnDetection`
     * except when an `BaseStreamingTurnDetector` instance fails the runtime preconditions
     * (no VAD, or RealtimeModel with server-side turn detection enabled), in
     * which case it is downgraded to `undefined` and a warning is logged.
     */
    private _resolvedTurnDetection;
    private pausedSpeech?;
    private falseInterruptionTimer?;
    private falseInterruptionPending;
    private cancelSpeechPauseTask?;
    private userTurnExceededLocked;
    private userTurnExceededTask?;
    private readonly onRealtimeGenerationCreated;
    private readonly onRealtimeInputSpeechStarted;
    private readonly onRealtimeInputSpeechStopped;
    private readonly onRealtimeInputAudioTranscriptionCompleted;
    private readonly onModelError;
    private readonly onInterruptionOverlappingSpeech;
    private readonly onInterruptionMetricsCollected;
    private readonly onInterruptionError;
    /** @internal */
    _mainTask?: Task<void>;
    _onEnterTask?: Task<void>;
    _onExitTask?: Task<void>;
    _userTurnCompletedTask?: Task<void>;
    _toolExecutor: ToolExecutor;
    constructor(agent: Agent, agentSession: AgentSession);
    start(options?: {
        reuseResources?: ReusableResources;
    }): Promise<void>;
    resume(options?: {
        reuseResources?: ReusableResources;
    }): Promise<void>;
    private _startSession;
    _detachReusableResources(newActivity: AgentActivity): Promise<ReusableResources>;
    get currentSpeech(): SpeechHandle | undefined;
    get vad(): VAD | undefined;
    /**
     * True iff the effective VAD for this activity is the framework-auto-provisioned
     * default. False when the user passed `vad=` to either the agent or the
     * session, even if the value happens to be the same silero model.
     */
    get usingDefaultVad(): boolean;
    get stt(): STT | undefined;
    private getSttProvider;
    get llm(): LLM | RealtimeModel | undefined;
    get tts(): TTS | undefined;
    get tools(): ToolContext;
    get schedulingPaused(): boolean;
    /** @internal */
    blockNewTurns(): void;
    pauseReplyAuthorization(): void;
    resumeReplyAuthorization(): void;
    get realtimeLLMSession(): RealtimeSession | undefined;
    get allowInterruptions(): boolean;
    get useTtsAlignedTranscript(): boolean;
    get turnDetection(): TurnDetectionMode | undefined;
    get turnHandling(): Partial<import("./turn_config/turn_handling.js").TurnHandlingOptions>;
    /**
     * Endpointing options resolved for this activity. Sparse session overrides
     * and the agent's own endpointing keys are merged, then defaults are filled
     * in based on this activity's resolved turn detector — so a streaming
     * ("audio model") detector uses the tighter streaming defaults while a
     * non-streaming agent in the same session keeps the legacy defaults.
     */
    get endpointingOpts(): EndpointingOptions;
    get maxEndpointingDelay(): number;
    get toolCtx(): ToolContext;
    /** @internal */
    get inputStartedAt(): number | undefined;
    /**
     * @internal — used by AMD to obtain a private branch of the participant
     * audio stream that does not interfere with the pipeline VAD/STT.
     */
    subscribeAudioStream(): ReadableStream<AudioFrame> | undefined;
    updateChatCtx(chatCtx: ChatContext): Promise<void>;
    updateInstructions(instructions: string | Instructions): Promise<void>;
    /**
     * Resolve the session's expressive setting. Returns `undefined` if disabled.
     *
     * Expressive mode requires three things, checked cheapest-first because this runs once
     * per speech segment:
     * - the session opted in.
     * - the inference gateway TTS ({@link inference.TTS}): the markup normalization/conversion
     *   and expressive chunking run there, so direct provider plugins would receive
     *   unconverted markup.
     * - a TTS that actually declares a markup dialect: gateway providers without one (e.g.
     *   `rime`, `deepgram`) get no markup instructions, so no tags can appear in the stream
     *   — leaving it "active" would enable xml-aware chunking with nothing to chunk and
     *   re-introduce the stray-`<` streaming stall. Asked via `markup.supported` rather than
     *   by rendering `llmInstructions()` and testing it for `undefined`: the blocks are
     *   several kilobytes, and every ordinary session would build and discard one per turn.
     *
     * @internal
     */
    _resolveExpressiveOptions(): ExpressiveOptions | undefined;
    /** Inject the TTS markup guide into the chat context. */
    private injectExpressiveInstructions;
    updateTools(tools: ToolContextLike<any>): Promise<void>;
    updateModels(options: AgentUpdateOptions): Promise<void>;
    updateOptions(options: {
        endpointing?: EndpointingOptions;
        toolChoice?: ToolChoice | null;
        turnDetection?: TurnDetectionMode | null;
    }): void;
    attachAudioInput(audioStream: ReadableStream<AudioFrame>): void;
    private shouldDiscardInputAudio;
    detachAudioInput(): void;
    commitUserTurn(options?: {
        audioDetached?: boolean;
        throwIfNotReady?: boolean;
    }): void;
    clearUserTurn(): void;
    say(text: string | ReadableStream<string>, options?: {
        audio?: ReadableStream<AudioFrame>;
        allowInterruptions?: boolean;
        addToChatCtx?: boolean;
    }): SpeechHandle;
    private onMetricsCollected;
    private pushConversationItemToStt;
    private onError;
    onInputSpeechStarted(_ev: InputSpeechStartedEvent): void;
    onInputSpeechStopped(ev: InputSpeechStoppedEvent): void;
    onInputAudioTranscriptionCompleted(ev: InputTranscriptionCompleted): void;
    onGenerationCreated(ev: GenerationCreatedEvent): void;
    onStartOfSpeech(ev: VADEvent): void;
    onEndOfSpeech(ev?: VADEvent): void;
    onVADInferenceDone(ev: VADEvent): void;
    onBackchannelConfirmed(): void;
    private interruptByAudioActivity;
    onInterruption(ev: OverlappingSpeechEvent): void;
    onInterimTranscript(ev: SpeechEvent, speaking: boolean | undefined): void;
    onFinalTranscript(ev: SpeechEvent, speaking: boolean | undefined): void;
    onTranscriptionTimeout(speechDuration: number, turnStart: number): void;
    /** Forward audio EOT predictions up to the session so listeners (e.g.
     * remote-session forwarders) can observe them. */
    onEotPrediction(ev: EotPredictionEvent): void;
    onAgentBackchannelOpportunity(_ev: _AgentBackchannelOpportunityEvent): void;
    onPreemptiveGeneration(info: PreemptiveGenerationInfo): void;
    onUserTurnExceeded(ev: UserTurnExceededEvent): void;
    private runUserTurnExceededTask;
    private cancelPreemptiveGeneration;
    private _interruptBackgroundSpeeches;
    private interruptQueuedSpeeches;
    private createSpeechTask;
    onEndOfTurn(info: EndOfTurnInfo): Promise<boolean>;
    retrieveChatCtx(): ChatContext;
    waitForIdle(options?: {
        waitForAgent?: boolean;
        waitForUser?: boolean;
    }): Promise<void>;
    private waitForEndOfTurn;
    private waitForInactive;
    private waitForOrAbort;
    private mainTask;
    private getDrainPendingSpeechTasks;
    private wakeupMainTask;
    private createAgentStateLease;
    private isAgentStateLeaseActive;
    private acquireAgentStateLease;
    private updateAgentState;
    private tryStartAgentSpeech;
    private releaseAgentStateLease;
    generateReply(options: {
        userMessage?: ChatMessage;
        chatCtx?: ChatContext;
        instructions?: string | Instructions;
        toolChoice?: ToolChoice | null;
        allowInterruptions?: boolean;
        scheduleSpeech?: boolean;
        inputDetails?: InputDetails;
    }): SpeechHandle;
    /**
     * Interrupt the current speech generation and any queued speeches.
     *
     * A queued speech that disallows interruptions keeps playing, along with the ones behind it,
     * unless `force` is set.
     *
     * @returns A future that completes when the interruption is fully processed.
     * @throws Error if the speech currently playing disallows interruptions and `force` is false.
     */
    interrupt(options?: {
        force?: boolean;
    }): Future<void>;
    private onPipelineReplyDone;
    _onEnterIgnoredTools(toolCtx: ToolContext): Tool[];
    /**
     * Commit a user turn whose reply is being skipped: append the transcript to the
     * agent chat context (when non-empty) without triggering reply generation.
     * Mirrors the python `_on_user_turn_completed` `skip_reply` branch.
     */
    private commitSkippedUserTurn;
    private userTurnCompleted;
    private ttsTask;
    /**
     * Reject `firstFrameFut` if it is still pending, dropping the PLAYBACK_STARTED
     * listener registered in `performAudioForwarding`. Called by the reply tasks once
     * the playout window has ended (finished or interrupted). `forwardAudio` no longer
     * settles the future itself so that a frame which plays late — after a
     * false-interruption resume (#1909) or a remote avatar's deferred
     * `lk.playback_started` notification (#1960) — can still resolve it.
     */
    private settleFirstFrameFut;
    private _pipelineReplyTaskImpl;
    /**
     * An agent turn is the convention's `invoke_agent`: the framework running the agent
     * in-process, with the inference (`chat`) and tool (`execute_tool`) spans nested underneath.
     */
    private recordAgentTurn;
    private pipelineReplyTask;
    private realtimeGenerationTask;
    private _realtimeGenerationTaskImpl;
    /**
     * Cancel the tool-execution task, tolerating a cancellation that overruns its budget.
     *
     * `Task.cancelAndWait` throws once the budget expires, and every caller still has work to do
     * afterwards — committing the interrupted tool outputs above all. A tool that ignores its abort
     * signal must degrade to a warning, not propagate and drop outputs the LLM has already seen.
     */
    private cancelToolExecutions;
    /** @internal */
    _waitForToolExecution({ executeToolsTask, toolOutput, speechHandle, }: {
        executeToolsTask: Pick<Task<void>, 'result' | 'cancelAndWait'>;
        toolOutput: ToolOutput;
        speechHandle: SpeechHandle;
    }): Promise<boolean>;
    /** @internal */
    _commitInterruptedToolOutputs(toolOutput: ToolOutput, speechHandle: SpeechHandle): void;
    private summarizeToolExecutionOutput;
    private realtimeReplyTask;
    private scheduleSpeech;
    /** @internal */
    _addDrainBlockedTasks(tasks: Task<any>[]): void;
    /** @internal */
    _withInlineTaskSlot<T>(options: {
        speechHandle?: SpeechHandle;
        blockedTasks: Task<any>[];
        fn: () => Promise<T>;
    }): Promise<T>;
    private _pauseSchedulingTask;
    private _resumeSchedulingTask;
    pause(options?: {
        blockedTasks?: Task<any>[];
        newActivity?: AgentActivity;
    }): Promise<ReusableResources | undefined>;
    drain(options?: {
        newActivity?: AgentActivity;
    }): Promise<ReusableResources | undefined>;
    private _drainImpl;
    close(): Promise<void>;
    private _resolveTurnDetection;
    private resolveInterruptionDetector;
    private updatePausedSpeech;
    private pauseEnabled;
    /** Preserve, apply, or release a speech pause before forwarding audio. */
    private reconcilePlayoutPause;
    private cancelFalseInterruptionTimer;
    private startFalseInterruptionTimer;
    /**
     * Clear a speech pause and optionally interrupt its handle.
     *
     * Final STT transcripts and committed turns that generate replies interrupt the paused
     * handle. Activity shutdown does not because the scheduling task owns the speech.
     */
    private cancelSpeechPause;
    /**
     * Disable VAD-based interruption either immediately or after the backchannel boundary
     * cooldown expires. While the cooldown is active the VAD path stays enabled, allowing the
     * user to correct themselves at the very start of agent speech.
     */
    private disableVadInterruptionSoon;
    private restoreInterruptionByAudioActivity;
    private fallbackToVadInterruption;
    private _closeSessionResources;
    private setupToolsets;
    private closeToolsets;
    /**
     * Refresh the agent's tool context after a dynamic toolset pushed a new tool list (via
     * `ToolsetContext.updateTools`), routing through updateTools so history, chat context, and the
     * realtime session stay in sync. The next LLM turn picks up the new tools automatically.
     */
    private onToolsetToolsChanged;
    private setupToolsetList;
    private closeToolsetList;
}
export {};
//# sourceMappingURL=agent_activity.d.ts.map