import type { AudioFrame } from '@livekit/rtc-node';
import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
import type { ReadableStream } from 'node:stream/web';
import type { TTSMetrics } from '../metrics/base.js';
import { type APIConnectOptions } from '../types.js';
import { AsyncIterableQueue } from '../utils.js';
import type { TimedString } from '../voice/io.js';
import { type MarkupInfo, type SpeechSteeringOptions } from './provider_format.js';
/**
 * SynthesizedAudio is a packet of speech synthesis as returned by the TTS.
 */
export interface SynthesizedAudio {
    /** Request ID (one segment could be made up of multiple requests) */
    requestId: string;
    /** Segment ID, each segment is separated by a flush */
    segmentId: string;
    /** Synthesized audio frame */
    frame: AudioFrame;
    /** Current segment of the synthesized audio */
    deltaText?: string;
    /** Whether this is the last frame of the segment (streaming only) */
    final: boolean;
    /**
     * Timed transcripts associated with this audio packet (word-level timestamps).
     */
    timedTranscripts?: TimedString[];
}
/**
 * Describes the capabilities of the TTS provider.
 *
 * @remarks
 * At present, only `streaming` is supplied to this interface, and the framework only supports
 * providers that do have a streaming endpoint.
 */
export interface TTSCapabilities {
    streaming: boolean;
    alignedTranscript?: boolean;
}
/**
 * Declares TTS markup capabilities for the expressive pipeline.
 *
 * Plugins opt in by overriding {@link TTS.markupProviderKey}: it selects which markup
 * dialect the TTS speaks — what the LLM is taught to write, and how those markers are
 * normalized and lowered to the provider's native syntax before synthesis. Stripping
 * markup back out is not here — the transcript sinks do it provider-agnostically (see
 * `splitAllMarkup`).
 */
export declare class TTSMarkup {
    #private;
    /** @internal */
    constructor(providerKey: () => string);
    /** Key into the shared `provider_format` markup tables, or `''` for none. */
    get providerKey(): string;
    /**
     * Whether this voice speaks a markup dialect at all.
     *
     * Allocation-free, unlike testing {@link llmInstructions} for `undefined` — which the
     * expressive gate does once per speech segment.
     */
    get supported(): boolean;
    /** The queryable markup matrix for this voice. */
    get info(): MarkupInfo;
    /**
     * Instructions for the LLM describing available markup tags.
     *
     * The framework injects this into the LLM system prompt when expressive mode is active.
     * Returns `undefined` if this TTS has no markup support. When `speechSteering` is given,
     * sounds it disables are omitted from the advertised vocabulary.
     */
    llmInstructions(options?: {
        speechSteering?: SpeechSteeringOptions;
    }): string | undefined;
    /** Fix common LLM markup mistakes (e.g. unclosed self-closing tags). */
    normalize(text: string): string;
    /**
     * Convert framework-standard markup to the provider's native format.
     *
     * Called before text is sent to the TTS; a no-op when the provider declares no markup.
     * Plugins that use non-XML formats (e.g. square brackets) opt in via
     * {@link TTS.markupProviderKey} so `<expression value="..."/>` becomes native syntax.
     */
    convert(text: string): string;
}
export interface TTSError {
    type: 'tts_error';
    timestamp: number;
    label: string;
    error: Error;
    recoverable: boolean;
}
export type TTSCallbacks = {
    ['metrics_collected']: (metrics: TTSMetrics) => void;
    ['error']: (error: TTSError) => void;
};
/**
 * Time at which text was first sent to the TTS provider, captured on both
 * clocks used for TTFB accounting: `time` on the `performance.now()` scale
 * (seconds, stamped on audio frames) and `hrTime` from
 * `process.hrtime.bigint()` (used for metrics durations).
 * @internal
 */
export interface SynthesizeStreamStartedTime {
    time: number;
    hrTime: bigint;
}
declare const TTS_base: new () => TypedEmitter<TTSCallbacks>;
/**
 * An instance of a text-to-speech adapter.
 *
 * @remarks
 * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that
 * exports its own child TTS class, which inherits this class's methods.
 */
export declare abstract class TTS extends TTS_base {
    #private;
    abstract label: string;
    constructor(sampleRate: number, numChannels: number, capabilities: TTSCapabilities);
    /**
     * Key into the shared markup tables, or `''` for none.
     *
     * Plugins override this to opt into markup support; the default (`''`) means no markup
     * instructions, normalization, or conversion are applied. Every {@link TTS.markup} method
     * delegates through this key, so a plugin only needs to override this one method.
     */
    protected markupProviderKey(): string;
    /** Access TTS markup capabilities (instructions for the LLM, text conversion). */
    get markup(): TTSMarkup;
    /**
     * Whether expressive is active for the current turn.
     * @internal
     */
    get expressive(): boolean;
    /**
     * Framework-internal: mark whether expressive is active for this turn.
     *
     * Called by the voice pipeline before each synthesis. TTS implementations widen their
     * input chunking when enabled; a no-op for TTS that don't tokenize their own input.
     *
     * @internal
     */
    _setExpressive(enabled: boolean): void;
    /** Returns this TTS's capabilities */
    get capabilities(): TTSCapabilities;
    /** Returns the sample rate of audio frames returned by this TTS */
    get sampleRate(): number;
    /** Returns the channel count of audio frames returned by this TTS */
    get numChannels(): number;
    /**
     * Get the model name/identifier for this TTS instance.
     *
     * @returns The model name if available, "unknown" otherwise.
     *
     * @remarks
     * Plugins should override this property to provide their model information.
     */
    get model(): string;
    /**
     * Get the provider name for this TTS instance.
     *
     * @returns The provider name if available, "unknown" otherwise.
     *
     * @remarks
     * Plugins should override this property to provide their provider information.
     */
    get provider(): string;
    /**
     * Receives text and returns synthesis in the form of a {@link ChunkedStream}
     */
    abstract synthesize(text: string, connOptions?: APIConnectOptions, abortSignal?: AbortSignal): ChunkedStream;
    /**
     * Returns a {@link SynthesizeStream} that can be used to push text and receive audio data
     *
     * @param options - Optional configuration including connection options
     */
    abstract stream(options?: {
        connOptions?: APIConnectOptions;
    }): SynthesizeStream;
    close(): Promise<void>;
}
/**
 * An instance of a text-to-speech stream, as an asynchronous iterable iterator.
 *
 * @example Looping through frames
 * ```ts
 * for await (const event of stream) {
 *   await source.captureFrame(event.frame);
 * }
 * ```
 *
 * @remarks
 * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that
 * exports its own child SynthesizeStream class, which inherits this class's methods.
 */
export declare abstract class SynthesizeStream implements AsyncIterableIterator<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM> {
    #private;
    protected static readonly FLUSH_SENTINEL: unique symbol;
    static readonly END_OF_STREAM: unique symbol;
    protected input: AsyncIterableQueue<string | typeof SynthesizeStream.FLUSH_SENTINEL>;
    protected queue: AsyncIterableQueue<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>;
    protected output: AsyncIterableQueue<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>;
    protected closed: boolean;
    protected connOptions: APIConnectOptions;
    protected abortController: AbortController;
    private deferredInputStream;
    private logger;
    abstract label: string;
    constructor(tts: TTS, connOptions?: APIConnectOptions);
    private _mainTaskImpl;
    private mainTask;
    private emitError;
    /**
     * Record a provider-known id for this stream on the current `tts_request_run`
     * span.
     *
     * Exposed on the span as `lk.provider_request_ids` so users can correlate
     * traces with the provider's server-side logs for debugging. Plugins should
     * call this when the provider-known id becomes available – either the
     * outbound `context_id`/`segment_id` (echoed back by the provider) or a
     * `request_id` / `session_id` returned in a response message. Deduped
     * internally, so calling on every message is fine.
     */
    protected noteProviderRequestId(contextId: string): void;
    /**
     * Mark the time at which text was first sent to the TTS provider for the
     * current segment. Used as the anchor for TTFB so upstream latency (LLM
     * streaming, sentence tokenization) is not attributed to the TTS. Only the
     * first call takes effect; the mark is reset after metrics are emitted for
     * the segment.
     *
     * Plugins should call this right before sending text to the provider.
     * Adapters wrapping another stream may pass that stream's
     * {@link SynthesizeStream.startedTime} to anchor on the wrapped provider.
     */
    protected markStarted(startedTime?: SynthesizeStreamStartedTime): void;
    /**
     * Time at which text was first sent to the provider for the current
     * segment, or `undefined` if nothing has been sent (or metrics for the
     * segment were already emitted).
     * @internal
     */
    get startedTime(): SynthesizeStreamStartedTime | undefined;
    protected pumpInput(): Promise<void>;
    /**
     * Set token usage for token-based TTS billing (e.g., OpenAI TTS).
     * Plugins should call this method to report token usage.
     */
    protected setTokenUsage({ inputTokens, outputTokens, }?: {
        inputTokens?: number;
        outputTokens?: number;
    }): void;
    protected monitorMetrics(): Promise<void>;
    /**
     * Called exactly once after the entire logical stream retry loop terminates.
     *
     * This runs after success, explicit close or abort, nonretryable failure, or exhausted retries,
     * but never between provider attempts. Subclasses may override it for whole-stream cleanup.
     */
    protected onStreamDone(): void;
    protected abstract run(): Promise<void>;
    updateInputStream(text: ReadableStream<string>): void;
    /** Push a string of text to the TTS */
    /** @deprecated Use `updateInputStream` instead */
    pushText(text: string): void;
    /** Flush the TTS, causing it to process all pending text */
    flush(): void;
    /** Mark the input as ended and forbid additional pushes */
    endInput(): void;
    next(): Promise<IteratorResult<SynthesizedAudio | typeof SynthesizeStream.END_OF_STREAM>>;
    get abortSignal(): AbortSignal;
    /** Close both the input and output of the TTS stream */
    close(): void;
    [Symbol.asyncIterator](): SynthesizeStream;
}
/**
 * An instance of a text-to-speech response, as an asynchronous iterable iterator.
 *
 * @example Looping through frames
 * ```ts
 * for await (const event of stream) {
 *   await source.captureFrame(event.frame);
 * }
 * ```
 *
 * @remarks
 * This class is abstract, and as such cannot be used directly. Instead, use a provider plugin that
 * exports its own child ChunkedStream class, which inherits this class's methods.
 */
export declare abstract class ChunkedStream implements AsyncIterableIterator<SynthesizedAudio> {
    #private;
    protected queue: AsyncIterableQueue<SynthesizedAudio>;
    protected output: AsyncIterableQueue<SynthesizedAudio>;
    protected closed: boolean;
    abstract label: string;
    private _connOptions;
    private logger;
    protected abortController: AbortController;
    constructor(text: string, tts: TTS, connOptions?: APIConnectOptions, abortSignal?: AbortSignal);
    private drainAttemptQueue;
    private _mainTaskImpl;
    private mainTask;
    private emitError;
    protected abstract run(): Promise<void>;
    get inputText(): string;
    get abortSignal(): AbortSignal;
    /**
     * Set token usage for token-based TTS billing (e.g., OpenAI TTS).
     * Plugins should call this method to report token usage.
     */
    protected setTokenUsage({ inputTokens, outputTokens, }?: {
        inputTokens?: number;
        outputTokens?: number;
    }): void;
    protected monitorMetrics(): Promise<void>;
    /** Collect every frame into one in a single call */
    collect(): Promise<AudioFrame>;
    next(): Promise<IteratorResult<SynthesizedAudio>>;
    /** Close both the input and output of the TTS stream */
    close(): void;
    [Symbol.asyncIterator](): ChunkedStream;
}
export {};
//# sourceMappingURL=tts.d.ts.map