import type { AudioFrame } from '@livekit/rtc-node';
import { EventEmitter } from 'node:events';
import type { ReadableStream } from 'node:stream/web';
import type { ChatContext } from '../llm/chat_context.js';
import type { ChatChunk } from '../llm/llm.js';
import type { ToolContext } from '../llm/tool_context.js';
import { MultiInputStream } from '../stream/multi_input_stream.js';
import type { SpeechEvent } from '../stt/stt.js';
import type { FlushSentinel } from '../types.js';
import type { ModelSettings } from './agent.js';
export type STTNode = (audio: ReadableStream<AudioFrame> | AsyncIterable<AudioFrame>, modelSettings: ModelSettings) => Promise<ReadableStream<SpeechEvent | string> | null>;
export type LLMNode = (chatCtx: ChatContext, toolCtx: ToolContext, modelSettings: ModelSettings) => Promise<ReadableStream<ChatChunk | string | FlushSentinel> | null>;
export type TTSNode = (text: ReadableStream<string> | AsyncIterable<string>, modelSettings: ModelSettings) => Promise<ReadableStream<AudioFrame> | null>;
/**
 * Symbol used to identify TimedString objects.
 */
export declare const TIMED_STRING_SYMBOL: unique symbol;
/**
 * A string with optional start and end timestamps for word-level alignment.
 */
export interface TimedString {
    readonly [TIMED_STRING_SYMBOL]: true;
    text: string;
    startTime?: number;
    endTime?: number;
    confidence?: number;
    startTimeOffset?: number;
    speakerId?: string | null;
}
/**
 * Factory function to create a TimedString object.
 */
export declare function createTimedString(opts: {
    text: string;
    startTime?: number;
    endTime?: number;
    confidence?: number;
    startTimeOffset?: number;
    speakerId?: string | null;
}): TimedString;
/**
 * Type guard to check if a value is a TimedString.
 */
export declare function isTimedString(value: unknown): value is TimedString;
export interface AudioOutputCapabilities {
    /** Whether this output supports pause/resume functionality */
    pause: boolean;
}
export declare abstract class AudioInput {
    protected multiStream: MultiInputStream<AudioFrame>;
    get stream(): ReadableStream<AudioFrame>;
    close(): Promise<void>;
    /**
     * Framework-owned attach-state transition used by {@link AgentInput.setAudioEnabled}.
     * Subclasses that gate frames (or wrap another {@link AudioInput}) should override
     * this — not only {@link onAttached}/{@link onDetached} — so mute still works when
     * lifecycle hooks omit `super`.
     * @internal
     */
    setAttached(_attached: boolean): void;
    /** Lifecycle hook invoked after {@link setAttached}(true). */
    onAttached(): void;
    /** Lifecycle hook invoked after {@link setAttached}(false). */
    onDetached(): void;
}
export declare abstract class AudioOutput extends EventEmitter {
    sampleRate?: number | undefined;
    protected readonly nextInChain?: AudioOutput | undefined;
    static readonly EVENT_PLAYBACK_STARTED = "playbackStarted";
    static readonly EVENT_PLAYBACK_FINISHED = "playbackFinished";
    static readonly EVENT_PLAYBACK_PROGRESSED = "playbackProgressed";
    private playbackFinishedFuture;
    private _capturing;
    private playbackFinishedCount;
    private playbackSegmentsCount;
    private lastPlaybackEvent;
    protected logger: import("pino").Logger;
    protected readonly capabilities: AudioOutputCapabilities;
    constructor(sampleRate?: number | undefined, nextInChain?: AudioOutput | undefined, capabilities?: AudioOutputCapabilities);
    /**
     * Whether this output and all outputs in the chain support pause/resume.
     */
    get canPause(): boolean;
    /**
     * Capture an audio frame for playback, frames can be pushed faster than real-time
     */
    captureFrame(_frame: AudioFrame): Promise<void>;
    /**
     * Wait for the past audio segments to finish playing out.
     *
     * @returns The event that was emitted when the audio finished playing out (only the last segment information)
     */
    waitForPlayout(): Promise<PlaybackFinishedEvent>;
    /**
     * Playback segments captured but not yet finished. Used by chained outputs to detect and
     * reconcile a segment-count drift against the next output in the chain.
     * @internal
     */
    get pendingPlayoutSegments(): number;
    /**
     * Monotonic count of playback segments ever captured. Lets chained outputs detect — free of
     * races with concurrent finishes — whether this output accepted a segment they forwarded.
     * @internal
     */
    get capturedPlayoutSegments(): number;
    /**
     * Called when playback actually starts (first frame is sent to output).
     * Developers building audio sinks should call this when the first frame is captured.
     */
    onPlaybackStarted(createdAt: number): void;
    /**
     * Report a stretch of the current segment that has played.
     *
     * Sinks that own their playback device report one run at a time; one that reports nothing is
     * described by its segment endpoints instead.
     */
    onPlaybackProgressed(ev: PlaybackProgressedEvent): void;
    /**
     * Developers building audio sinks must call this method when a playback/segment is finished.
     * Segments are segmented by calls to flush() or clearBuffer()
     */
    onPlaybackFinished(options: PlaybackFinishedEvent): void;
    flush(): void;
    /**
     * Forget the segment currently being captured, without treating it as a flush boundary.
     *
     * For an output whose open segment was abandoned rather than flushed — e.g. a capture threw and
     * the segment has already been reported finished. Without this the output keeps believing a
     * segment is open, so the next `captureFrame` silently joins a segment that no longer exists
     * instead of counting a new one.
     * @internal
     */
    abandonOpenSegment(): void;
    /**
     * Clear the buffer, stopping playback immediately
     */
    abstract clearBuffer(): void;
    onAttached(): void;
    onDetached(): void;
    /**
     * Pause the audio playback
     */
    pause(): void;
    /**
     * Resume the audio playback
     */
    resume(): void;
}
export interface PlaybackFinishedEvent {
    /** How much of the audio was played back, in seconds */
    playbackPosition: number;
    /** True if playback was interrupted (clearBuffer() was called) */
    interrupted: boolean;
    /**
     * Transcript synced with playback; may be partial if the audio was interrupted.
     * When undefined, the transcript is not synchronized with the playback.
     */
    synchronizedTranscript?: string;
}
export interface PlaybackStartedEvent {
    /** The timestamp (Date.now()) when the playback started */
    createdAt: number;
}
/**
 * A stretch of the current segment that has played.
 *
 * Reported once the audio can no longer be discarded, so it is never revised.
 */
export interface PlaybackProgressedEvent {
    /** The timestamp (Date.now()) at which this stretch began to play */
    startedAt: number;
    /** Where it starts in the audio captured for the current segment, in milliseconds */
    offset: number;
    /** How much of it played, in milliseconds */
    duration: number;
}
export declare abstract class TextOutput {
    protected readonly nextInChain?: TextOutput | undefined;
    constructor(nextInChain?: TextOutput | undefined);
    abstract captureText(text: string | TimedString): Promise<void>;
    /**
     * Mark the current text segment as complete (e.g LLM generation is complete)
     */
    abstract flush(): void;
    onAttached(): void;
    onDetached(): void;
}
export declare class AgentInput {
    private readonly audioChanged;
    private readonly audioEnabledChanged?;
    private _audioStream;
    private _audioEnabled;
    constructor(audioChanged: () => void, audioEnabledChanged?: ((enabled: boolean) => void) | undefined);
    setAudioEnabled(enable: boolean): void;
    get audioEnabled(): boolean;
    get audio(): AudioInput | null;
    set audio(stream: AudioInput | null);
    private applyAudioAttachState;
}
export declare class AgentOutput {
    private readonly audioChanged;
    private readonly transcriptionChanged;
    private _audioSink;
    private _transcriptionSink;
    private _audioEnabled;
    private _transcriptionEnabled;
    constructor(audioChanged: () => void, transcriptionChanged: () => void);
    setAudioEnabled(enabled: boolean): void;
    setTranscriptionEnabled(enabled: boolean): void;
    get audioEnabled(): boolean;
    get transcriptionEnabled(): boolean;
    get audio(): AudioOutput | null;
    set audio(sink: AudioOutput | null);
    get transcription(): TextOutput | null;
    set transcription(sink: TextOutput | null);
}
//# sourceMappingURL=io.d.ts.map