/** * Split off the leading run of complete sentences from `text`, returning the * speakable prefix and the still-incomplete remainder. A segment is "complete" * when it ends in terminal punctuation (optionally followed by closing quotes). */ export declare function takeCompleteVoiceText(text: string): { text: string; remaining: string; }; export declare function isCompleteVoiceText(text: string): boolean; /** Join two voice-text fragments, normalizing whitespace at the seam. */ export declare function appendVoiceText(existing: string, next: string): string; /** * Normalize a speakable segment so TTS does not read formatting aloud. * * LLM output is full of markdown — `**bold**`, `## heading`, `` `code` ``, `[text](url)` — * and a TTS engine narrates the punctuation literally ("star star bold star star"). This is * the single highest-frequency voice bug class and has no app-layer fix, because the text is * generated inside the pipeline. Vapi ships a 14-step "voice formatting plan" on by default; * this is the locale-free core of it: markdown removal. * * Deliberately conservative — it strips *formatting* markers, never content, so a sentence * that legitimately contains an asterisk or a hash in prose is left intact where ambiguous. * Number/currency/date verbalization is locale-sensitive and intentionally NOT done here yet; * doing it wrong (wrong locale, wrong magnitude) speaks worse than leaving the digits. That is * a separate stage, not a silent omission. */ /** * Strip leaked tool-call protocol tokens so TTS never speaks them. * * A model emits tool calls in its own syntax (`<|tool_call|>…`, `…`, * `[TOOL_CALLS]`, harmony `<|channel|>commentary`). The inference server is supposed to parse * that into the structured `tool_calls` field; when the serving stack lacks the right parser * the tokens fall through as ordinary assistant text and the pipeline speaks the markup aloud. * LiveKit's finding: the same weights score 100% behind one endpoint and 0% behind another — * it is the serving stack, not the model. Syrinx (and the Kuralle runtime) both read only the * finalized structured tool-call and never text-scrape, so neither catches a leaked one. * * This is a high-precision guard: it removes only unambiguous sentinel tokens/blocks that never * occur in real speech. It does NOT try to strip bare JSON function calls — that is ambiguous and * stripping legitimate content is worse than the rare leak. A leak of that shape is a serving-stack * bug to fix at the endpoint (LiveKit's "one curl" diagnosis), not something to paper over here. */ export declare function stripLeakedToolCalls(text: string): string; export declare function normalizeForSpeech(text: string): string; /** * The FIRST spoken fragment of a turn, taken earlier than a full sentence. * * Only the first dispatch gates time-to-first-audio: every later sentence is * synthesised while earlier audio is already playing, so its buffering is free. * But the sentence rule charges the first chunk the full wait for a terminator — * measured at ~200ms of a ~950ms TTFA. * * This takes a leading fragment at a CLAUSE boundary (comma, semicolon, colon, * dash) once it is long enough to be worth speaking. Never mid-word: the split is * always at punctuation followed by whitespace. * * Deliberately conservative about the cases `isCompleteVoiceText` guards — it will * not split on a decimal point or an abbreviation dot, because those are not * clause boundaries and never match here. * * Returns empty when no safe early split exists, in which case the caller keeps * waiting for a full sentence. */ export declare function takeFirstFragment(text: string, minChars: number): { text: string; remaining: string; }; //# sourceMappingURL=voice-text.d.ts.map