import type { Observable } from 'rxjs'; import type { MediaError } from './errors'; import type { PlayOptions, PresayOptions } from './mixer'; /** * Arguments for {@link import('./media-channel').MediaChannel.createTts}. * * Mirrors {@link import('./asr-handle').AsrConfig} credential selection for TTS: * when Voctiv platform compatibility is on, **`name`** selects a row from * **`authentication_data.legacyTtsKeysByName`**. You may also set the same selector * as **`data.name`** (stripped before vendor params are built). * * Call **`createTts`** early in the dialog to open / warm the vendor connection * (HTTP keep-alive pool or streaming WebSocket) so later {@link TtsHandle.say} * / {@link TtsHandle.say$} / {@link TtsHandle.presay} calls reuse it instead of * paying SSL handshake latency on every utterance. */ export interface TtsConfig { /** * TTS vendor / engine hint, e.g. `"elevenlabs"`, `"google"`, `"azure"`, `"voctiv"`. * Resolved via ScriptEngine vendor aliases. If you set **`name`** but omit **`vendor`**, * the runtime may infer vendor from the key row's **`platform`** in the catalog. */ vendor?: string; /** * logic-executor **`key_storage.name`** for this dialog's agent + company. Selects credentials * from **`authentication_data.legacyTtsKeysByName[name]`** when Voctiv platform PostgreSQL key auth is enabled. * Overrides channel **`defaultTtsName`**. */ name?: string; /** * Vendor-specific connection parameters (voice id, model, `output_format`, nested JSON, …). * Merged last over channel defaults and catalog credentials so the script can override * per session. Primitives are stringified; objects and arrays are JSON-serialized. * * Do not rely on **`name`** here for third-party "model name" fields — the runtime consumes * it as the storage row selector and removes it before vendor config is built. */ data?: Record; } /** * Playback options for {@link TtsHandle.say} / {@link TtsHandle.say$}. * * Vendor / credentials come from {@link import('./media-channel').MediaChannel.createTts}; * only mixer and strategy fields apply here. */ export type TtsSayOptions = Omit; /** * Options for {@link TtsHandle.presay}. * * Vendor / credentials come from {@link import('./media-channel').MediaChannel.createTts}. */ export type TtsPresayOptions = Omit; /** Why a tracked utterance was cancelled before natural completion. */ export type TtsUtteranceCancelReason = 'stop' | 'destroy'; /** * Lifecycle events for one {@link TtsHandle.say$} invocation. * * Typical sequence (sentence strategy): * `queued` → (`speaking` → `done`)×N then Observable **complete**. * `done` means one sentence/phrase finished playback (not the whole `say$` call). * Single-item / streaming strategy: one `speaking` → one `done` then **complete**. * On barge-in / `audio.stop`: `queued` → (`speaking`?) → `cancelled` then **complete**. * On synthesis failure: `queued` → (`speaking`?) then the Observable **errors** with * {@link MediaError} (also mirrored on {@link TtsHandle.error$} / `channel.events.error$`). */ export type TtsUtteranceEvent = { state: 'queued'; alias: string; queue: number; /** * Full utterance text known so far. * Plain string input: complete text. Token stream: grows as chunks arrive * (initial `queued` may have `text: ''`). */ text: string; } | { state: 'speaking'; alias: string; queue: number; /** Full utterance text accumulated so far (stream) or the whole input (string). */ text: string; /** * Text of the sentence/segment currently starting playback when the host * uses sentence-split aliases (`alias-0`, `alias-1`, …). * For a single-item utterance (streaming strategy / exact alias) this equals {@link text}. */ sentenceText: string; /** Index of the sentence segment (`0` for `alias-0`), when applicable. */ sentenceIndex?: number; /** Concrete mixer item alias (may be `alias-0` for sentence-split TTS). */ itemAlias?: string; } | { state: 'done'; alias: string; queue: number; /** Full utterance text accumulated so far (stream) or the whole input (string). */ text: string; /** * Text of the sentence/segment that just finished playback. * Same rules as {@link TtsUtteranceEvent} `speaking.sentenceText`. */ sentenceText: string; /** Index of the sentence segment (`0` for `alias-0`), when applicable. */ sentenceIndex?: number; /** Concrete mixer item alias that finished. */ itemAlias?: string; } | { state: 'cancelled'; alias: string; queue: number; reason: TtsUtteranceCancelReason; /** Accumulated text at cancel time. */ text: string; }; /** * Pre-warmed TTS session returned by {@link import('./media-channel').MediaChannel.createTts}. * * Use **`say`** / **`say$`** / **`presay`** on this handle (same idea as * {@link import('./asr-handle').AsrHandle} methods) so synthesis reuses the cached * connector / streaming WebSocket. Call **`destroy()`** when done * (e.g. on `channel.events.terminated$`) to release the session. * * You may still pass the handle as {@link import('./mixer').PlayOptions.tts} to * `channel.audio.say` / `presay` if needed. * * If connector creation fails, SIP/WS return a degraded handle: `say`/`say$`/`presay` fall back * to an ephemeral connection, and the creation failure is reported on * {@link import('./media-channel').ChannelEvents.error$}. * * ```ts * const tts = await channel.createTts({ name: 'elevenlabs-main' }); * tts.error$.subscribe(err => console.log('TTS error:', err.message)); * * await tts.say('Hello', { alias: 'greeting' }); * * tts.say$('Next', { alias: 'reply' }).subscribe({ * next: (e) => { if (e.state === 'speaking') console.log('now playing', e.alias); }, * error: (err) => console.log('TTS failed', err.message), * }); * * tts.destroy(); * ``` */ export interface TtsHandle { /** Opaque id for this pre-warmed TTS session. */ readonly id: string; /** * Runtime errors from the TTS provider for this session (auth failures, disconnects, etc.). * * A degraded handle (returned when connector creation itself failed) has an inert `error$` * that never emits — the creation failure is reported on {@link import('./media-channel').ChannelEvents.error$} instead. */ readonly error$: Observable; /** * Synthesize and play text on a mixer queue, reusing this session's connector. * * Resolves when playback of this invocation has finished (or was aborted). * For per-utterance lifecycle (`queued` / `speaking` / `done` / …) use {@link say$}. */ say(input: string | Observable, options?: TtsSayOptions): Promise; /** * Same synthesis path as {@link say}, but emits {@link TtsUtteranceEvent} for this utterance. * * Only available on handles from {@link import('./media-channel').MediaChannel.createTts} * (not on `channel.audio.say`). Emits `done` per finished sentence; Observable **completes** * when the whole call ends (or after `cancelled`). Synthesis failures go to the Observable * **error** channel as {@link MediaError}. * * ```ts * tts.say$('One. Two.', { alias: 'greet', queue: 0 }).subscribe({ * next: (e) => { * switch (e.state) { * case 'queued': break; // e.text — full string (or '' for live stream) * case 'speaking': break; // e.sentenceText — sentence starting playback * case 'done': break; // e.sentenceText — that sentence finished * case 'cancelled': break; // stop / destroy * } * }, * complete: () => {}, // whole say$ finished * error: (err: MediaError) => console.log('TTS failed', err.message), * }); * ``` */ say$(input: string | Observable, options?: TtsSayOptions): Observable; /** * Pre-synthesize text into the host TTS cache using this session's connector. * * Same behaviour as {@link import('./media-channel').ChannelAudio.presay}. */ presay(text: string, options?: TtsPresayOptions): Promise; /** Tear down the cached connector / streaming socket. Idempotent-safe on well-behaved hosts. */ destroy(): void; } //# sourceMappingURL=tts-handle.d.ts.map