{"version":3,"sources":["../../src/tts/provider_format.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Shared provider-specific TTS formatting logic.\n *\n * Both TTS plugins and the inference gateway delegate to this module so there is a single\n * source of truth for LLM instructions and markup stripping per provider.\n *\n * Provider docs:\n * - Cartesia: https://docs.cartesia.ai/build-with-cartesia/sonic-3/ssml-tags\n * - Cartesia: https://docs.cartesia.ai/build-with-cartesia/sonic-3/volume-speed-emotion\n * - Inworld: https://docs.inworld.ai/tts/capabilities/steering\n * - Inworld: https://docs.inworld.ai/tts/best-practices/prompting-for-tts-2\n * - xAI: https://docs.x.ai/developers/model-capabilities/audio/text-to-speech\n * - xAI: https://docs.x.ai/developers/model-capabilities/audio/voice\n * - Fish Audio: https://docs.fish.audio/developer-guide/core-features/emotions\n */\nimport { ATTRIBUTE_TRANSCRIPTION_EXPRESSION } from '../constants.js';\nimport { basic as tokenizeBasic } from '../tokenize/index.js';\nimport type { SentenceTokenizer } from '../tokenize/tokenizer.js';\nimport { type TimedString, createTimedString } from '../voice/io.js';\nimport {\n  LEADING_WS,\n  convertExpressionTags,\n  dedupRemovalSpace,\n  escapeRegExp,\n  extractAndStrip,\n  replaceWithGroups,\n} from './markup_utils.js';\nimport { matchMood } from './mood.js';\n\n/**\n * An expressive markup tag stripped from a transcript, surfaced for the frontend.\n *\n * `type` is the markup tag name (`\"emotion\"`, `\"expression\"`, `\"sound\"`, ...) or the expr\n * marker type. `value` is the spoken or semantic payload (the `value=\"...\"` attribute, the\n * expr `label`, or the tag's inner text).\n */\nexport interface ExpressiveTag {\n  type: string;\n  value: string;\n}\n\n/**\n * Non-verbal vocalizations the TTS may produce (sounds that aren't words).\n *\n * A sparse opt-out: omitted keys default to ON, and a category set to `false` is never\n * advertised to the LLM — `{ laughing: false }` removes laughter and nothing else.\n * Together the keys cover every sound the providers offer.\n */\nexport interface NonverbalOptions {\n  /** laugh, chuckle, giggle — and laugh-speak delivery */\n  laughing?: boolean;\n  /** audible breath, inhale, exhale */\n  breathing?: boolean;\n  sighing?: boolean;\n  crying?: boolean;\n  /** non-lexical voiced sounds — humming a tune, sing-song or sung delivery */\n  vocalizing?: boolean;\n  /** tsk, tongue-click, lip-smack */\n  mouthSounds?: boolean;\n  /** cough, clearing the throat, yawn */\n  reflexSounds?: boolean;\n}\n\n/**\n * Steers verbal delivery and non-verbal sounds in generated speech.\n *\n * Every key is a sparse override on the default (full sound vocabulary, light fillers):\n * the expressive instructions already tell the LLM to match its delivery to the register\n * of the moment, so most agents need no steering at all — set a key only to take an option\n * away regardless of context.\n */\nexport interface SpeechSteeringOptions {\n  /**\n   * Filler words such as \"um\" / \"uh\". On by default\n   * ({@link DEFAULT_SPEECH_STEERING_OPTIONS}); set `false` to opt out.\n   */\n  disfluencies?: boolean;\n  /**\n   * Which non-verbal sounds the TTS may make. `true` (and omitting the key) keeps the\n   * provider's full vocabulary, `false` disables every sound, and a\n   * {@link NonverbalOptions} object toggles per category (omitted categories stay enabled).\n   */\n  nonverbalSounds?: boolean | NonverbalOptions;\n  pace?: 'slow' | 'normal' | 'fast';\n}\n\n/** The default steering: full sound vocabulary, light fillers. */\nexport const DEFAULT_SPEECH_STEERING_OPTIONS: SpeechSteeringOptions = { disfluencies: true };\n\n/** What the expressive markup pipeline can do with a given voice. */\nexport interface MarkupInfo {\n  /** {@link NonverbalOptions} field -> the labels it governs; an absent field is a no-op */\n  nonverbals: Partial<Record<NonverbalField, string[]>>;\n}\n\ntype NonverbalField = keyof NonverbalOptions;\n\nconst CARTESIA_TAGS = ['emotion', 'speed', 'volume', 'break', 'spell'];\n\nconst INWORLD_TAGS = ['expression', 'sound', 'break'];\n\n// xAI Grok TTS speech tags, from the xAI docs\n// (https://docs.x.ai/developers/rest-api-reference/inference/voice).\n//\n// The LLM is instructed in the expr dialect (below); these native tag names serve two\n// purposes: XAI_WRAPPING is the label vocabulary expr prosody markers lower to, and all\n// of them stay in XAI_TAGS so a hallucinated native tag is still stripped from the\n// transcript rather than leaking. The intermediate <sound value=\"NAME\"/> and\n// <break time=\"...\"/> tags that expr lowering produces are rewritten to xAI's native\n// brackets by convertMarkup — <sound value=\"X\"/> -> [X] and <break> -> [pause] or\n// [long-pause] by duration. Prosody is angle-bracketed (native).\nconst XAI_INLINE = [\n  'breath',\n  'inhale',\n  'exhale',\n  'sigh',\n  'laugh',\n  'chuckle',\n  'giggle',\n  'cry',\n  'tsk',\n  'tongue-click',\n  'lip-smack',\n  'hum-tune',\n];\nconst XAI_WRAPPING = [\n  'emphasis', // stress the wrapped words\n  'whisper', // quiet, intimate\n  'soft', // lower volume\n  'loud', // higher volume\n  'build-intensity', // ramp energy up over the span\n  'decrease-intensity', // ease energy off over the span\n  'higher-pitch',\n  'lower-pitch',\n  'slow',\n  'fast',\n  'sing-song', // playful, musical lilt\n  'singing', // actually sung\n  'laugh-speak', // talk through a laugh\n];\nconst XAI_EMOTIONS = [\n  'happy',\n  'sad',\n  'angry',\n  'excited',\n  'calm',\n  'surprised',\n  'sympathetic',\n  'curious',\n  'sarcastic',\n  'confident',\n  'playful',\n  'nervous',\n];\n// all tags are XML in the transcript, so all are stripped. inline sounds are the single\n// \"sound\" tag (<sound value=\"NAME\"/>, XAI_INLINE lists the NAMEs), and pauses use\n// \"break\" (<break time=\"...\"/>), both modeled on Inworld.\nconst XAI_TAGS = [...XAI_EMOTIONS, ...XAI_WRAPPING, 'sound', 'break'];\n\n// xAI has two pause levels ([pause], [long-pause]); map an Inworld-style <break time=\"X\"/>\n// to the longer one past ~1s. This is the only per-provider bit convertMarkup needs.\nconst XAI_BREAK_RE = /<break\\s+time=\"([^\"]*)\"\\s*\\/?>/g;\n\nfunction parseDurationSeconds(raw: string): number {\n  const value = raw.trim().toLowerCase();\n  const parsed = value.endsWith('ms')\n    ? Number.parseFloat(value.slice(0, -2)) / 1000\n    : Number.parseFloat(value.replace(/s+$/, ''));\n  return Number.isNaN(parsed) ? 0 : parsed;\n}\n\nfunction xaiBreakToBracket(_match: string, time: string): string {\n  return parseDurationSeconds(time) >= 1 ? '[long-pause]' : '[pause]';\n}\n\n// Fish Audio (s2 family) speech markers, from the Fish docs\n// (https://docs.fish.audio/developer-guide/core-features/emotions).\n//\n// The LLM is instructed in the expr dialect (below); expr lowering produces the\n// framework-standard intermediates (<expression value=\"...\"/>, <sound value=\"...\"/>,\n// <break time=\"...\"/>, <emphasis>word</emphasis>) and convertMarkup rewrites them to\n// Fish's native square brackets: [very EMOTION], [SOUND], [break]/[long-break], and\n// [emphasis] word (a prefix marker stressing the word that follows). Tone wrapping\n// (<expr type=\"prosody\" label=\"whispering\">...</expr>) lowers directly to Fish's prefix\n// form, [whispering] followed by the span. The tag names stay in FISHAUDIO_TAGS so\n// hallucinated native markup is still stripped from transcripts.\n//\n// Every label below is from Fish's documented vocabulary, and every emotion maps to a\n// non-fallback mood in mood.ts so lk.expression stays meaningful for clients.\nconst FISHAUDIO_EMOTIONS = [\n  'regretful',\n  'hopeful',\n  'happy',\n  'excited',\n  'curious',\n  'surprised',\n  'sad',\n  'empathetic',\n  'sarcastic',\n  'calm',\n  'angry',\n  'worried',\n  'nervous',\n  'confident',\n  'grateful',\n  'delighted',\n  'disappointed',\n  'frustrated',\n  'determined',\n];\nconst FISHAUDIO_SOUNDS = [\n  'laughing',\n  'chuckling',\n  'clear throat',\n  'sighing',\n  'gasping',\n  'groaning',\n  'yawning',\n  'sobbing',\n];\n// Fish's tone controls: prefix markers steering the delivery of the words after them.\n// Neutral delivery styles, so they are never steering-filtered (same stance as xAI's\n// whisper/pitch wraps).\nconst FISHAUDIO_TONES = ['whispering', 'soft', 'shouting', 'hurried'];\nconst FISHAUDIO_TAGS = ['expression', 'sound', 'break', 'emphasis'];\n\nconst FISHAUDIO_EXPRESSION_RE = /<expression\\s+value=\"([^\"]*)\"(?:\\s*\\/>|>(?:.*?)<\\/expression>)/g;\nconst FISHAUDIO_BREAK_RE = /<break\\s+time=\"([^\"]*)\"\\s*\\/?>/g;\nconst FISHAUDIO_EMPHASIS_RE = /<emphasis(?:\\s[^>]*)?>([^<]*)<\\/emphasis\\s*>/gi;\n\nfunction fishaudioExpressionToBracket(_match: string, value: string): string {\n  // intensify with a leading \"very\" so the emotion lands harder in Fish's audio\n  // ([very regretful] steers more strongly than [regretful]); never doubled\n  let label = value.trim();\n  if (label && !label.toLowerCase().startsWith('very ')) {\n    label = `very ${label}`;\n  }\n  return `[${label}]`;\n}\n\nfunction fishaudioBreakToBracket(_match: string, time: string): string {\n  // Fish has two pause levels ([break], [long-break]); use the longer past ~1s\n  return parseDurationSeconds(time) >= 1 ? '[long-break]' : '[break]';\n}\n\n// --- LiveKit expression markers (expr) ---\n// The LLM emits a single marker tag, <expr type=\"...\" label=\"...\"/>, instead of\n// provider-native tags. The *syntax* is shared, but each provider gets its own instruction\n// block advertising only the types and label vocabularies it actually supports — providers\n// offer different sound effects, some take only a discrete emotion vocabulary rather than\n// free-form delivery descriptions, and only some have wrapping prosody. Types (per\n// provider):\n//   expression (self-closing) - delivery/emotion for what follows; free-form for\n//                               Inworld, Cartesia's discrete emotion vocabulary, absent\n//                               for xAI\n//   break      (self-closing) - pause, label is a duration (\"500ms\", \"1s\"); all providers\n//   sound      (self-closing) - non-verbal vocalization from the provider's own list\n//                               (Inworld: laugh/sigh/..., xAI: chuckle/tsk/...); absent\n//                               for Cartesia\n//   prosody    (wrapping)     - <expr type=\"prosody\" label=\"whisper\">words</expr>, labels\n//                               from xAI's wrapping-tag list; for Cartesia a self-closing\n//                               point control (slow/fast/soft/loud -> coarse speed/volume\n//                               ratios); absent for Inworld (folded into expression)\n//   spell      (wrapping)     - <expr type=\"spell\">A7X9</expr> character-by-character\n//                               readout; Cartesia only\n// convertMarkup lowers expr to each provider's native syntax before synthesis (via the\n// existing framework-standard tags, so the per-provider conversions below still apply),\n// and the transcript strippers remove expr markers in a dedicated pre-pass so the\n// type/label pair surfaces correctly as an ExpressiveTag. This is the only dialect the\n// LLM is taught — llmInstructions() uses it; the provider-native tag tables remain solely\n// so hallucinated native markup is still stripped/converted instead of leaking.\n\nconst EXPR_PREAMBLE = `You control speech delivery with a single XML marker tag: <expr/>. Every marker has a \\\ntype attribute. Use only the marker types listed below, and where a type lists a label \\\nvocabulary, only those labels. Use the markers often and diversify them so the voice \\\nnever sounds flat while ensuring the markers are appropriate for the moment. Write the \\\nwords themselves the way people talk: use contractions (\"I'm\", \"you're\", \"don't\") — \\\nspelled-out forms like \"I am\" or \"do not\" sound stiff when spoken.\n\nJust as important is knowing when NOT to reach for a marker. Reserve surprise openers \\\nlike \"oh\" or \"ah\" for genuine surprise — an ordinary request isn't one. Don't stack markers \\\non short replies or decorate every sentence. If a reaction wouldn't happen in a real \\\nconversation, skip it — there's always another genuine beat to lean into.\n\nMatch your delivery to the REGISTER of the moment, and reassess every turn. When the \\\nmoment is professional, high-stakes, or emotionally heavy — bad news, an emergency, \\\nreal distress — keep delivery composed and restrained. When the moment is casual, \\\nplayful, or celebratory, let it loosen and brighten. A serious turn in an otherwise \\\ncasual conversation still gets a composed reply.`;\n\nconst CARTESIA_EXPR_LLM_INSTRUCTIONS =\n  EXPR_PREAMBLE +\n  `\n\n1. Emotion - sets the emotional tone. Self-closing; place before EVERY sentence.\n   <expr type=\"expression\" label=\"EMOTION\"/>\n   Labels are a fixed vocabulary, NOT free-form descriptions. Best results: neutral, \\\nangry, excited, content, sad, scared.\n   Also available: happy, enthusiastic, elated, triumphant, amazed, surprised, \\\nflirtatious, curious, peaceful, serene, calm, grateful, affectionate, sympathetic, \\\nmysterious, frustrated, disgusted, sarcastic, ironic, dejected, melancholic, \\\ndisappointed, apologetic, hesitant, confused, anxious, panicked, proud, confident, \\\ncontemplative, determined, joking/comedic.\n\n2. Pauses - insert silence when appropriate. Self-closing.\n   <expr type=\"break\" label=\"1s\"/> - label is a duration in seconds or milliseconds.\n\n3. Prosody - adjusts pacing and loudness from that point on. Self-closing.\n   <expr type=\"prosody\" label=\"slow\"/> slower    <expr type=\"prosody\" label=\"fast\"/> faster\n   <expr type=\"prosody\" label=\"soft\"/> quieter    <expr type=\"prosody\" label=\"loud\"/> louder\n   Labels are a fixed vocabulary: slow, fast, soft, loud.\n\n4. Spell - wraps text read character by character (codes, IDs, or a spelled-out name).\n   <expr type=\"spell\">A7X9</expr>\n   Keep punctuation out of a spell marker — a period inside is read as \"dot\"; add \\\nspaces inside for grouped pauses (<expr type=\"spell\">ABC 123</expr>).\n\nThis voice has no non-verbal sounds and no free-form delivery descriptions — do not \\\ninvent other types or labels.\n\nExamples:\n  <expr type=\"expression\" label=\"excited\"/> I can't wait to tell you! <expr type=\"expression\" label=\"happy\"/> This is going to be great!\n  <expr type=\"expression\" label=\"curious\"/> Really? <expr type=\"break\" label=\"500ms\"/> <expr type=\"expression\" label=\"excited\"/> Tell me more!\n  Your code is <expr type=\"spell\">A7X9</expr>. <expr type=\"break\" label=\"1s\"/> <expr type=\"expression\" label=\"calm\"/> Got it?`;\n\nconst INWORLD_SOUNDS = ['laugh', 'sigh', 'breathe', 'clear throat', 'cough', 'yawn'];\n\nconst INWORLD_EXAMPLES = [\n  '<expr type=\"expression\" label=\"say really playfully\"/> Okay okay, why did the burger go to the gym? <expr type=\"break\" label=\"500ms\"/> <expr type=\"expression\" label=\"really bright, a little fast\"/> Because it wanted better buns! <expr type=\"sound\" label=\"laugh\"/>',\n  '<expr type=\"expression\" label=\"a little sheepish, apologetic\"/> Ah man, yeah that\\'s on us. <expr type=\"expression\" label=\"speak really calmly\"/> Lemme see what I can do.',\n  '<expr type=\"sound\" label=\"sigh\"/> <expr type=\"expression\" label=\"speak softly, almost a whisper\"/> I know it\\'s been a rough week.',\n  '<expr type=\"expression\" label=\"really amiable and welcoming\"/> Welcome to the hotel. <expr type=\"expression\" label=\"gently inquisitive, slightly fast\"/> How can I help you today?',\n  '<expr type=\"expression\" label=\"gently easygoing and reassuring\"/> That\\'s all set. <expr type=\"break\" label=\"300ms\"/> <expr type=\"expression\" label=\"slow and really clearly enunciated\"/> Your confirmation code is B 4 J 7.',\n  // persona carried into the tags: casual words, casual labels\n  '<expr type=\"expression\" label=\"really chill, a little fast\"/> Yeah, of course! <expr type=\"expression\" label=\"casual, almost fast\"/> Gimme one sec, pulling it up now.',\n];\n\n/** Drop example lines that demonstrate a *vocabulary* label not in `allowed`. */\nfunction soundExamples(examples: string[], allowed: string[], vocabulary: string[]): string[] {\n  const removed = vocabulary.filter((s) => !allowed.includes(s));\n  return examples.filter((ex) => !removed.some((s) => ex.includes(`label=\"${s}\"`)));\n}\n\nfunction numberedSections(sections: string[]): string {\n  return sections.map((section, i) => `${i + 1}. ${section}`).join('\\n\\n');\n}\n\nfunction inworldExprLlmInstructions(sounds: string[]): string {\n  const sections = [\n    `Delivery - controls how a sentence sounds. Self-closing; place before EVERY sentence.\n   <expr type=\"expression\" label=\"DESCRIPTION\"/>\n   The label is free-form: describe vocal quality, pitch, volume, pace, and intonation \\\nin plain English — \"say really playfully\", \"slightly surprised, amiable\", \"sound a little \\\nconcerned\", \"drop to almost a whisper\", \"speak really slowly and clearly, patient and \\\nreassuring\".\n   Match the expression tag's energy to the sentence's punctuation. An exclamation \\\nneeds a bright or upbeat label (e.g. \"bright, upbeat energy\"); a calm or reassuring \\\nlabel flattens the \"!\". Never lead an exclamatory sentence with a calm tag.\n   Put each question in its own sentence — don't comma-splice it onto a statement. \\\nWrite \"Welcome to the hotel. How can I help you today?\", not \"Welcome to the hotel, \\\nhow can I help you today?\", so the question carries its own delivery tag instead of \\\ninheriting the statement's.\n   Never put \"questioning\" in a tag — describe the mood alone and let the question \\\nmark carry the intonation.\n   Name a mood or speaking style, not a mechanical pitch contour. \"gently upbeat, \\\namiable\" steers far more reliably than \"rising tone\".\n   Use at most two adjectives per tag, and make sure they align — with the mood of \\\nthe sentence and with each other. Clashing descriptors (\"calm, excited\") cancel out \\\nand muddy the delivery.\n   Put a degree modifier in EVERY tag — \"a little\", \"almost\", \"slightly\", \"gently\", \\\n\"really\" — to set the exact strength of the feeling: \"a little amused\" or \"almost a \\\nwhisper\" lands truer than \"amused\" or \"whisper\", and \"really excited\" turns the \\\ndelivery up when the moment truly peaks. Most moments call for a shade, not the \\\nextreme — default to the softeners and save \"really\" for true peaks.\n   Carry your persona into the tags — the labels should sound like the character, \\\nnot generic stage directions. An amiable, casual persona tags with \"really relaxed \\\nand amiable\" or \"casual, a little playful\"; a formal concierge tags the same \\\nsentence \"gently courteous, composed\". Delivery that contradicts who you are reads \\\nas a different speaker.\n   Don't open a turn with a \"slow\" tag. The first expression colors the whole turn, \\\nand a slow lead flattens questions and drags the energy down. Keep the pace neutral \\\nby default and reserve slow, clearly-enunciated delivery for the specific line that \\\nneeds it (a total, date, address, or confirmation code).\n   Rotate expression labels — don't reuse the same one two turns in a row, and vary \\\nthe descriptor. A starting palette:\n     greeting / amiable open: \"really amiable and welcoming\" / \"gently bright, \\\nheartfelt\" / \"cheerful, really glad you called\"\n     asking a question: \"gently upbeat and amiable\" / \"really open and inquisitive\" / \\\n\"gently inquisitive, attentive\"\n     good news / exclamation: \"really bright, upbeat energy\" / \"really delighted and \\\nglad\" / \"gently pleased and bright\"\n     reassuring / taking a request in stride: \"really calm and confident\" / \\\n\"gently easygoing and reassuring\" / \"really relaxed and grounded\"\n     empathy / a problem or bad news: \"really soft, with tender care\" / \\\n\"gently concerned, caring\" / \"almost a murmur, gentle and steady\"\n     reading back a total, date, or code: \"slow and really clearly enunciated\"`,\n  ];\n\n  if (sounds.length) {\n    const fits = ' (a clear-throat when shifting to a new step or topic, for example)';\n    let section = `Sounds - a non-verbal sound between sentences. Self-closing.\n   <expr type=\"sound\" label=\"${sounds[0]}\"/>\n   Labels are a fixed vocabulary: ${sounds.join(', ')}.\n   Use non-verbal sounds sparingly, and never the same one twice in a row — reach for \\\none only where it genuinely fits${sounds.includes('clear throat') ? fits : ''}. An enabled \\\nsound gets over-used otherwise.`;\n    if (sounds.includes('breathe')) {\n      section += `\n   Use the \"breathe\" sound only for a real, gentle breath, never as filler — on this \\\nmodel it easily reads as a weary or impatient sigh, which sounds wrong in a support \\\nsetting.`;\n    }\n    sections.push(section);\n  }\n\n  sections.push(`Pauses - insert silence when appropriate. Self-closing.\n   <expr type=\"break\" label=\"500ms\"/> or <expr type=\"break\" label=\"1s\"/> (max 10s).\n   A period or an ellipsis (...) already creates a pause, so don't put a break marker \\\nright next to one — pick one or the other.\n   After any <expr type=\"break\"/>, give the sentence that follows its own expression \\\ntag — a fresh one, not necessarily the same as before (a break is often where the mood \\\nshifts). A break resets delivery to neutral, so an untagged sentence after a break is \\\nspoken flat.`);\n\n  const parts = [\n    EXPR_PREAMBLE,\n    numberedSections(sections),\n    'There is no wrapping prosody marker for this voice — put pace, pitch, and volume in ' +\n      'the expression label instead.',\n    `Write for the EAR, not the page: no em or en dashes anywhere in spoken text — \\\nuse a comma or a period for a short beat, or a break marker for a real pause. Avoid \\\nsemicolons, mid-sentence colons, and parenthetical asides; rewrite them as separate \\\nsentences or commas.`,\n    `When the conversation is in another language, still write every marker label in \\\nEnglish — delivery descriptions and sound names steer the voice and are never \\\ntranslated.`,\n  ];\n\n  if (sounds.includes('laugh')) {\n    parts.push(\n      'Laughter belongs only in genuinely playful or celebratory beats, never at ' +\n        'a serious moment.',\n    );\n  }\n\n  const examples = soundExamples(INWORLD_EXAMPLES, sounds, INWORLD_SOUNDS);\n  if (examples.length) {\n    parts.push('Examples:\\n' + examples.map((ex) => `  ${ex}`).join('\\n'));\n  }\n  return parts.join('\\n\\n');\n}\n\nconst XAI_EXAMPLES = [\n  'So I walked in and <expr type=\"break\" label=\"500ms\"/> <expr type=\"sound\" label=\"inhale\"/> there it was! <expr type=\"prosody\" label=\"whisper\">It was a secret the whole time.</expr>',\n  '<expr type=\"prosody\" label=\"build-intensity\">This is going to be so good.</expr> <expr type=\"prosody\" label=\"loud\">I can\\'t wait!</expr>',\n  '<expr type=\"prosody\" label=\"soft\">Hey.</expr> <expr type=\"sound\" label=\"sigh\"/> <expr type=\"prosody\" label=\"lower-pitch\">I know it\\'s been a rough week.</expr> I\\'m right here.',\n  '<expr type=\"prosody\" label=\"higher-pitch\">You did not just say that</expr> okay, <expr type=\"prosody\" label=\"fast\">tell me everything.</expr>',\n  // sound-free, so at least one example survives any steering filter; the break lands\n  // mid-sentence before the key detail, never beside sentence punctuation\n  '<expr type=\"prosody\" label=\"emphasis\">Everything</expr> is confirmed for <expr type=\"break\" label=\"500ms\"/> Thursday the <expr type=\"prosody\" label=\"emphasis\">ninth</expr>. <expr type=\"prosody\" label=\"slow\">Is there anything else I can help you with?</expr>',\n];\n\nfunction xaiExprLlmInstructions(sounds: string[], prosody: string[]): string {\n  const sections: string[] = [];\n  if (sounds.length) {\n    sections.push(`Sounds - a non-verbal vocalization at the exact point where it happens. Self-closing.\n   <expr type=\"sound\" label=\"${sounds[0]}\"/>\n   Labels are a fixed vocabulary: ${sounds.join(', ')}.\n   Use non-verbal sounds sparingly, and never the same one twice in a row — reach for \\\none only where it genuinely fits. An enabled sound gets over-used otherwise.`);\n  }\n\n  sections.push(`Pauses - insert silence when appropriate. Self-closing.\n   <expr type=\"break\" label=\"500ms\"/> a brief pause    <expr type=\"break\" label=\"1s\"/> a longer, dramatic pause\n   NEVER place a break next to a period, question mark, exclamation point, or ellipsis \\\n— sentence punctuation already pauses, and a break beside it double-pauses. Most \\\nreplies need no break markers at all; reserve them for a deliberate mid-sentence beat \\\nbefore a key detail (a date, a name, a number).`);\n\n  const tones = prosody.filter((p) => p !== 'emphasis');\n  sections.push(`Prosody - wraps a span delivered in a distinct style, to shape HOW it's said.\n   <expr type=\"prosody\" label=\"STYLE\">the words it affects</expr>\n   Labels are a fixed vocabulary: ${tones.join(', ')}.\n   Use one only where the moment clearly calls for it — most sentences need none. \\\nNever nest one prosody marker inside another, and always close it with </expr>.`);\n\n  sections.push(`Emphasis - stresses exactly the ONE word it wraps.\n   Are you <expr type=\"prosody\" label=\"emphasis\">sure</expr> you want to do this?\n   Wrap a single word, never a phrase, and never write it in all-caps — caps are read \\\nout as individual letters. Never nest it, and always close it with </expr>.`);\n\n  const parts = [\n    EXPR_PREAMBLE,\n    numberedSections(sections),\n    'This voice has no free-form delivery descriptions — shape delivery entirely through ' +\n      (sounds.length ? 'prosody markers, sounds, pauses' : 'prosody markers, pauses') +\n      ', punctuation, and word choice.',\n    `Write for the EAR, not the page: no em or en dashes anywhere in spoken text — \\\nuse a comma or a period for a short beat, or a break marker for a real pause. Avoid \\\nsemicolons, mid-sentence colons, and parenthetical asides; rewrite them as separate \\\nsentences or commas.`,\n    `When the conversation is in another language, still write every marker label in \\\nEnglish — labels are a fixed vocabulary, never translated.`,\n    `Key details deserve care: stress the load-bearing word of a date, amount, or \\\nname with the emphasis marker, and wrap a dense or easy-to-mishear span in \\\n<expr type=\"prosody\" label=\"slow\">...</expr>. Read codes and reference numbers \\\ncharacter by character, spelled out with spaces, so each one lands.`,\n  ];\n\n  // Vocabulary-specific register guidance on top of the preamble's neutral rule,\n  // mentioning only concepts this steering leaves enabled (whisper/soft/loud are\n  // neutral delivery controls, never filtered).\n  const register = [\n    'Whisper and soft belong to gentle or conspiratorial beats; loud only to ' +\n      'genuinely high-energy ones.',\n  ];\n  if (['laugh', 'chuckle', 'giggle'].some((s) => sounds.includes(s))) {\n    register.push(\n      'Laughter is RARE: a laugh, chuckle, or giggle belongs only where something ' +\n        'is genuinely funny — friendliness, agreement, or mild amusement is not a ' +\n        'reason, and never laugh at your own lines. Most replies have no laughter ' +\n        'at all.',\n    );\n  }\n  parts.push(register.join(' '));\n\n  const examples = soundExamples(\n    XAI_EXAMPLES,\n    [...sounds, ...prosody],\n    [...XAI_INLINE, ...XAI_WRAPPING],\n  );\n  if (examples.length) {\n    parts.push('Examples:\\n' + examples.map((ex) => `  ${ex}`).join('\\n'));\n  }\n  return parts.join('\\n\\n');\n}\n\n// Examples carried over from the original Fish expressive block (PR #6232), rewritten\n// in the expr dialect. Breaks appear only mid-sentence, never beside a period/?/! —\n// an example pairing a break with sentence punctuation few-shots the LLM into\n// double-pausing every boundary.\nconst FISHAUDIO_EXAMPLES = [\n  '<expr type=\"expression\" label=\"excited\"/> That\\'s hilarious! <expr type=\"sound\" label=\"laughing\"/> <expr type=\"expression\" label=\"happy\"/> You always lighten the mood.',\n  '<expr type=\"expression\" label=\"empathetic\"/> <expr type=\"sound\" label=\"clear throat\"/> That sounds like a <expr type=\"prosody\" label=\"emphasis\">really</expr> difficult experience.',\n  '<expr type=\"expression\" label=\"sad\"/> Oh, my goodness <expr type=\"sound\" label=\"clear throat\"/> <expr type=\"break\" label=\"2s\"/> that\\'s a real shame.',\n  '<expr type=\"expression\" label=\"frustrated\"/> <expr type=\"sound\" label=\"sighing\"/> I\\'ve been going in circles with this all morning. <expr type=\"expression\" label=\"determined\"/> Okay. One more try.',\n  // sound-free, so at least one example survives any steering filter\n  '<expr type=\"expression\" label=\"happy\"/> You\\'re all set for <expr type=\"break\" label=\"500ms\"/> Thursday the <expr type=\"prosody\" label=\"emphasis\">ninth</expr>. <expr type=\"expression\" label=\"curious\"/> Is there anything else I can help you with?',\n  // sound-free tone example: the wrap is scoped to the span, not the sentence\n  '<expr type=\"expression\" label=\"delighted\"/> <expr type=\"prosody\" label=\"whispering\">Okay, don\\'t tell anyone yet</expr> <expr type=\"expression\" label=\"excited\"/> but I think we actually pulled it off!',\n];\n\n// The original block baked light disfluencies into the few-shots — that's what made\n// fillers actually show up in generations. Appended only while steering has\n// disfluencies enabled, so the examples never contradict the \"no fillers\" guideline.\nconst FISHAUDIO_DISFLUENT_EXAMPLES = [\n  '<expr type=\"expression\" label=\"curious\"/> Um, uh... really? <expr type=\"expression\" label=\"sad\"/> Well, I\\'m really sorry to hear that.',\n  '<expr type=\"expression\" label=\"regretful\"/> I really wish I\\'d, um, called sooner. <expr type=\"expression\" label=\"hopeful\"/> But I\\'m here now if, if you want to talk.',\n  '<expr type=\"expression\" label=\"surprised\"/> What?! No way! I, I\\'m flabbergasted! <expr type=\"expression\" label=\"sarcastic\"/> Fair play, I guess.',\n];\n\nfunction fishaudioExprLlmInstructions(sounds: string[], disfluencies = true): string {\n  const sections = [\n    `Emotion - sets how a sentence sounds. Self-closing; place at the START of a sentence.\n   <expr type=\"expression\" label=\"EMOTION\"/>\n   Labels are a fixed vocabulary, NOT free-form descriptions: ${FISHAUDIO_EMOTIONS.join(', ')}.\n   Give every sentence its own emotion marker — repeat the same label to carry a \\\nfeeling across sentences, or switch labels when the feeling shifts.`,\n  ];\n\n  if (sounds.length) {\n    sections.push(`Sounds - a non-verbal sound between sentences. Self-closing.\n   <expr type=\"sound\" label=\"${sounds[0]}\"/>\n   Labels are a fixed vocabulary: ${sounds.join(', ')}.\n   Use non-verbal sounds sparingly, and never the same one twice in a row — reach for \\\none only where it genuinely fits. An enabled sound gets over-used otherwise.`);\n  }\n\n  sections.push(`Pauses - insert silence when appropriate. Self-closing.\n   <expr type=\"break\" label=\"500ms\"/> or <expr type=\"break\" label=\"2s\"/>.\n   NEVER place a break next to a period, question mark, exclamation point, or ellipsis \\\n— sentence punctuation already pauses, and a break beside it double-pauses. Most \\\nreplies need no break markers at all; reserve them for a deliberate mid-sentence beat \\\nbefore a key detail (a date, a name, a number).`);\n\n  sections.push(`Tone - wraps a span delivered in a distinct style.\n   <expr type=\"prosody\" label=\"whispering\">don't tell anyone yet.</expr>\n   Labels are a fixed vocabulary: ${FISHAUDIO_TONES.join(', ')}.\n   Use a tone only where the moment clearly calls for one — most sentences need \\\nnone. Never nest tone markers, and always close the tag with </expr>.`);\n\n  sections.push(`Emphasis - stresses exactly the ONE word it wraps.\n   Are you <expr type=\"prosody\" label=\"emphasis\">sure</expr> you want to do this?\n   Wrap a single word, never a phrase. Never nest it, and always close it with </expr>.`);\n\n  const parts = [\n    EXPR_PREAMBLE,\n    numberedSections(sections),\n    `Write for the EAR, not the page: no em or en dashes anywhere in spoken text — \\\nuse a comma or a period for a short beat, or a break marker for a real pause. Avoid \\\nsemicolons, mid-sentence colons, and parenthetical asides; rewrite them as separate \\\nsentences or commas.`,\n    `When the conversation is in another language, still write every marker label in \\\nEnglish — labels are a fixed vocabulary, never translated.`,\n  ];\n\n  // Vocabulary-specific register guidance on top of the preamble's neutral rule.\n  // Each clause mentions only concepts this steering actually enables, so an\n  // opted-out option is never referenced (not even prohibitively).\n  const register = [\n    'At heavy moments reach for empathetic, sad, regretful, or hopeful — never a ' +\n      'bright label like \"happy\" or \"excited\" against hard news; bright labels belong ' +\n      'to bright moments.',\n    'Whispering and soft belong to gentle or conspiratorial beats; shouting only to ' +\n      'genuinely high-energy ones.',\n  ];\n  if (['laughing', 'chuckling'].some((s) => sounds.includes(s))) {\n    register.push(\n      'Laughter belongs only in genuinely playful or celebratory beats, never at ' +\n        'a serious moment.',\n    );\n  }\n  if (disfluencies) {\n    register.push(\n      'Save fillers for relaxed moments — never in an emergency or against grave news.',\n    );\n  }\n  parts.push(register.join(' '));\n\n  const pool = [...FISHAUDIO_EXAMPLES, ...(disfluencies ? FISHAUDIO_DISFLUENT_EXAMPLES : [])];\n  const examples = soundExamples(pool, sounds, FISHAUDIO_SOUNDS);\n  if (examples.length) {\n    parts.push('Examples:\\n' + examples.map((ex) => `  ${ex}`).join('\\n'));\n  }\n  return parts.join('\\n\\n');\n}\n\n// Every provider's full expr sound vocabulary (the advertised labels before any\n// speechSteering filtering). Providers absent here have no non-verbal sounds.\nconst PROVIDER_SOUNDS: Record<string, string[]> = {\n  inworld: INWORLD_SOUNDS,\n  xai: XAI_INLINE,\n  fishaudio: FISHAUDIO_SOUNDS,\n};\n\ntype NonverbalTable = Record<string, Partial<Record<NonverbalField, string[]>>>;\n\n/**\n * Labels from a per-provider governance table that `steering` disables.\n *\n * `nonverbalSounds` accepts a boolean or a sparse per-category object: `true` (like\n * omitting the key) keeps the full vocabulary, `false` disables every sound, and in an\n * object an omitted category stays ENABLED — `{ laughing: false }` removes laughter and\n * nothing else.\n */\nfunction steeringRemoved(\n  table: NonverbalTable,\n  provider: string,\n  steering: SpeechSteeringOptions | undefined,\n): Set<string> {\n  const nonverbals = steering?.nonverbalSounds;\n  const labels = table[provider];\n  if (nonverbals === undefined || nonverbals === true || labels === undefined) {\n    return new Set();\n  }\n  if (nonverbals === false) {\n    return new Set(Object.values(labels).flat());\n  }\n  const removed = new Set<string>();\n  for (const [field, fieldLabels] of Object.entries(labels) as [NonverbalField, string[]][]) {\n    if (nonverbals[field] === false) {\n      for (const label of fieldLabels) removed.add(label);\n    }\n  }\n  return removed;\n}\n\n/**\n * The provider's sound vocabulary minus labels steering disables.\n *\n * Every label is governed by a {@link NonverbalOptions} field, so passing\n * `nonverbalSounds: false` returns an empty list — the instruction builders then omit the\n * Sounds section entirely.\n */\nfunction allowedSounds(provider: string, steering: SpeechSteeringOptions | undefined): string[] {\n  const removed = steeringRemoved(NONVERBAL_SOUND_LABELS, provider, steering);\n  return (PROVIDER_SOUNDS[provider] ?? []).filter((s) => !removed.has(s));\n}\n\n/**\n * The provider's wrapping-prosody vocabulary minus labels steering disables.\n *\n * Unlike sounds, only the vocal-style labels (laugh-speak, singing, ...) are governed —\n * neutral delivery controls (emphasis, whisper, pitch, pace) always survive, so the result\n * is never empty.\n */\nfunction allowedProsody(provider: string, steering: SpeechSteeringOptions | undefined): string[] {\n  const removed = steeringRemoved(NONVERBAL_PROSODY_LABELS, provider, steering);\n  return (PROVIDER_PROSODY[provider] ?? []).filter((p) => !removed.has(p));\n}\n\n// NonverbalOptions field -> the provider's expr sound labels it governs. A provider\n// absent here (cartesia) has no non-verbal sounds; an empty list means the provider\n// has no sound for that field (nothing to filter). allowedSounds uses this to remove\n// disabled labels from the advertised vocabulary, so a sound steering turns off is never\n// exposed to the LLM in the first place. Every label in PROVIDER_SOUNDS must be governed\n// by exactly one field, so a steering config controls the full vocabulary.\nconst NONVERBAL_SOUND_LABELS: NonverbalTable = {\n  inworld: {\n    laughing: ['laugh'],\n    breathing: ['breathe'],\n    sighing: ['sigh'],\n    crying: [],\n    vocalizing: [],\n    mouthSounds: [],\n    reflexSounds: ['cough', 'clear throat', 'yawn'],\n  },\n  xai: {\n    laughing: ['laugh', 'chuckle', 'giggle'],\n    breathing: ['breath', 'inhale', 'exhale'],\n    sighing: ['sigh'],\n    crying: ['cry'],\n    vocalizing: ['hum-tune'], // non-lexical voiced sounds\n    mouthSounds: ['tsk', 'tongue-click', 'lip-smack'],\n    reflexSounds: [], // xAI has no cough/yawn sounds\n  },\n  fishaudio: {\n    laughing: ['laughing', 'chuckling'],\n    breathing: ['gasping'],\n    sighing: ['sighing'],\n    crying: ['sobbing'],\n    vocalizing: ['groaning'],\n    mouthSounds: [],\n    reflexSounds: ['clear throat', 'yawning'],\n  },\n};\n\n// NonverbalOptions field -> the provider's wrapping-prosody labels it governs.\n// Sparse on purpose: only vocal-style prosody (talking through a laugh, singing)\n// is steerable; neutral delivery controls are never filtered.\nconst NONVERBAL_PROSODY_LABELS: NonverbalTable = {\n  xai: {\n    laughing: ['laugh-speak'],\n    vocalizing: ['sing-song', 'singing'],\n  },\n};\n\n// Every provider's full wrapping-prosody vocabulary (only xAI has one).\nconst PROVIDER_PROSODY: Record<string, string[]> = {\n  xai: XAI_WRAPPING,\n};\n\n/** {@link NonverbalOptions} field -> the sound/prosody labels it governs for `provider`. */\nexport function supportedNonverbals(provider: string): Partial<Record<NonverbalField, string[]>> {\n  const merged: Partial<Record<NonverbalField, string[]>> = {};\n  for (const table of [NONVERBAL_SOUND_LABELS, NONVERBAL_PROSODY_LABELS]) {\n    for (const [field, labels] of Object.entries(table[provider] ?? {}) as [\n      NonverbalField,\n      string[],\n    ][]) {\n      if (labels.length) {\n        merged[field] = [...(merged[field] ?? []), ...labels];\n      }\n    }\n  }\n  return merged;\n}\n\n// Sound label -> when a real speaker would make it. The sounds guideline is composed\n// from the hints of whichever labels survived steering, so the LLM only ever reads\n// usage advice for sounds it's allowed to make. Labels sharing a hint (the laugh\n// family) collapse to one clause; labels without an entry fall back to the generic\n// sentence. Keyed by label, not NonverbalOptions field, so it's provider-agnostic.\nconst SOUND_USAGE_HINTS: Record<string, string> = {\n  laugh: 'a laugh at something obviously funny',\n  laughing: 'a laugh at something obviously funny',\n  chuckle: 'a chuckle at something subtly humorous',\n  chuckling: 'a chuckle at something subtly humorous',\n  giggle: 'a chuckle at something subtly humorous',\n  sigh: 'a sigh when commiserating',\n  sighing: 'a sigh when commiserating',\n  inhale: 'a sharp inhale before a big reveal',\n  gasping: 'a gasp at a sudden shock or reveal',\n  'lip-smack': 'a lip-smack or tongue-click as a tiny beat of thought',\n  'tongue-click': 'a lip-smack or tongue-click as a tiny beat of thought',\n  tsk: 'a tsk for mock-disapproval',\n  'clear throat': 'a clear-throat when shifting to a new step or topic',\n  groaning: 'a groan at a groan-worthy pun or an unwelcome chore',\n  yawning: 'a yawn when tiredness itself is the topic',\n  sobbing: 'a sob reserved for real heartbreak',\n};\n\n/** The sparing-use guideline, illustrated only with the allowed sounds. */\nfunction soundGuidance(sounds: string[]): string {\n  const hints: string[] = [];\n  for (const sound of sounds) {\n    const hint = SOUND_USAGE_HINTS[sound];\n    if (hint && !hints.includes(hint)) hints.push(hint);\n  }\n  let line = 'Non-verbal sounds: use one only where the moment genuinely earns it';\n  if (hints.length) line += ' — ' + hints.join(', ');\n  return line + '. Most turns have none; never repeat the same sound twice in a row.';\n}\n\n/**\n * Render a {@link SpeechSteeringOptions} into delivery guidelines for `provider`.\n *\n * Only fields that change the default produce output, so an empty object adds nothing on\n * top of the base template. Disabled sounds never appear here: {@link llmInstructions}\n * filters them out of the advertised vocabulary, so the only sound guidance left is how\n * sparingly to use what remains.\n */\nexport function steeringInstructions(provider: string, steering: SpeechSteeringOptions): string {\n  const lines: string[] = [];\n\n  // sound guidance only when steering actually removes part of the vocabulary:\n  // the explicit all-on forms (true, an empty object) must render identically to\n  // omitting the key, and all-off leaves nothing to guide\n  if (steeringRemoved(NONVERBAL_SOUND_LABELS, provider, steering).size) {\n    const allowed = allowedSounds(provider, steering);\n    if (allowed.length) lines.push(soundGuidance(allowed));\n  }\n\n  if (steering.disfluencies !== undefined) {\n    lines.push(\n      steering.disfluencies\n        ? 'Sprinkle in natural fillers (um, uh) and openers (oh, well, so), ' +\n            'zero to two per turn, never mechanical.'\n        : 'No fillers (um, uh). Sound composed and fluent.',\n    );\n  }\n\n  if (steering.pace !== undefined && steering.pace !== 'normal') {\n    lines.push(`Keep a ${steering.pace} overall speaking pace.`);\n  }\n\n  if (!lines.length) return '';\n  return 'Delivery guidelines:\\n' + lines.map((line) => `- ${line}`).join('\\n');\n}\n\n// Hard per-provider chunking defaults (characters). The value caps every synthesis\n// request at the provider's send limit and, under expressive, doubles as the batch size\n// so sentences are grouped up to it. Providers absent here are uncapped and always emit\n// per sentence.\nconst MAX_INPUT_LEN: Record<string, number> = {\n  inworld: 900,\n  cartesia: 400,\n  // well under xAI's 15,000-char request limit; sized as an expressive batch\n  // target (https://docs.x.ai/developers/model-capabilities/audio/text-to-speech)\n  xai: 1000,\n  // fishaudio is deliberately absent: its markers are sentence-scoped (every sentence\n  // carries its own [very EMOTION]), so per-sentence emission loses no steering and keeps\n  // time-to-first-audio low\n};\n\n/** The max text chunk length for a provider, or `undefined` if unlimited. */\nexport function maxInputLen(provider: string): number | undefined {\n  return MAX_INPUT_LEN[provider];\n}\n\n/**\n * How much text an expressive turn batches before emitting, in characters — roughly two\n * sentences.\n *\n * Deliberately far below the providers' request caps. The cap is a transport limit\n * (400–1000 chars); using it as the batch target means a typical reply never reaches it,\n * so nothing is emitted while the LLM streams and the whole turn is synthesized in one\n * request once generation ends — time-to-first-audio becomes \"wait for the full\n * completion\". Batching a couple of sentences is all continuous prosody needs.\n */\nconst EXPRESSIVE_BATCH_LEN = 200;\n\n/**\n * Minimum length of the first chunk of an expressive turn — the tokenizer's per-sentence\n * default, so the opening sentence is sent the moment it is complete.\n *\n * Batching starts from the second chunk, which keeps prosody continuous over the body of\n * the turn while leaving time-to-first-audio identical to a non-expressive turn.\n */\nconst EXPRESSIVE_FIRST_CHUNK_LEN = 20;\n\n/**\n * Default sentence tokenizer for a provider's streamed TTS input.\n *\n * The provider's hard max chunk length caps every emitted token. When `expressive` is set,\n * it also raises the *minimum* to {@link EXPRESSIVE_BATCH_LEN} so a couple of consecutive\n * sentences ride one request, keeping prosody continuous across the turn; otherwise tokens\n * emit per sentence (the unchanged default). Providers with no configured limit are\n * uncapped and stay per-sentence even under expressive — Fish Audio's markers are\n * sentence-scoped, so batching would cost time-to-first-audio and buy no steering.\n */\nexport function sentenceTokenizer(\n  provider: string,\n  options: { expressive: boolean },\n): SentenceTokenizer {\n  const maxLen = MAX_INPUT_LEN[provider];\n  const batching = options.expressive && maxLen !== undefined;\n  return new tokenizeBasic.SentenceTokenizer({\n    maxTokenLength: maxLen,\n    // the batch target is independent of the cap; clamped so it can never exceed it\n    minTokenLength: batching ? Math.min(EXPRESSIVE_BATCH_LEN, maxLen!) : undefined,\n    firstTokenLength: batching ? EXPRESSIVE_FIRST_CHUNK_LEN : undefined,\n    // markup only exists in the stream when expressive is active; xml-aware\n    // tokenization would otherwise hold streaming on a stray \"<\" in plain text\n    xmlAware: options.expressive,\n  });\n}\n\nconst EXPR_ATTR_RE = /([\\w-]+)\\s*=\\s*\"([^\"]*)\"/g;\n// every marker pattern captures the space before it as \"pre\" so dedupRemovalSpace can\n// drop it when the marker vanishes from between two spaces\n// any <expr ...> or <expr .../> tag (open or self-closing)\nconst EXPR_OPEN_RE = new RegExp(LEADING_WS + '<expr\\\\b(?<attrs>[^>]*?)/?\\\\s*>', 'g');\nconst EXPR_CLOSE_RE = new RegExp(LEADING_WS + '</expr\\\\s*>', 'g');\n// self-closing markers only (the trailing / is required)\nconst EXPR_SELF_RE = new RegExp(LEADING_WS + '<expr\\\\b(?<attrs>[^>]*?)/\\\\s*>', 'g');\n// a wrapping marker (prosody/spell) and its span; non-greedy, instructed not to nest.\n// The `(?<!/)` is load-bearing: Cartesia's prosody markers are self-closing point\n// controls, so without it `<expr type=\"prosody\" label=\"slow\"/>` reads as an *opening*\n// tag whose span runs to the next `</expr>` — swallowing every marker in between. A\n// `<expr type=\"spell\">` caught that way is discarded, and the confirmation code it\n// wrapped is spoken as a word instead of spelled out.\nconst EXPR_WRAP_RE = new RegExp(\n  LEADING_WS +\n    '<expr\\\\b(?=[^>]*type=\"(?:prosody|spell)\")(?<attrs>[^>]*?)(?<!/)>(?<inner>.*?)</expr\\\\s*>',\n  'gs',\n);\n// a non-wrapping type the LLM forgot to self-close (normalizeMarkup fixes these)\nconst EXPR_UNCLOSED_RE = /(<expr\\b(?=[^>]*type=\"(?:expression|break|sound)\")[^>]*[^/>\\s])\\s*>/g;\n\n// expr sound labels that differ from xAI's native cue names\nconst XAI_SOUND_ALIASES: Record<string, string> = { breathe: 'breath' };\n\n// expr sound labels that differ from Fish's native marker names (other providers\n// advertise \"laugh\"/\"chuckle\", so a hallucinated one still lowers to a sound Fish renders)\nconst FISHAUDIO_SOUND_ALIASES: Record<string, string> = {\n  laugh: 'laughing',\n  chuckle: 'chuckling',\n  sigh: 'sighing',\n  gasp: 'gasping',\n  groan: 'groaning',\n  yawn: 'yawning',\n  sob: 'sobbing',\n  cry: 'sobbing',\n};\n\n// Cartesia prosody labels -> native point controls (coarse steps of the numeric ratios)\nconst CARTESIA_PROSODY: Record<string, string> = {\n  slow: '<speed ratio=\"0.85\"/>',\n  fast: '<speed ratio=\"1.2\"/>',\n  soft: '<volume ratio=\"0.9\"/>',\n  loud: '<volume ratio=\"1.3\"/>',\n};\n\nfunction exprAttrs(attrs: string): Record<string, string> {\n  const out: Record<string, string> = {};\n  for (const match of attrs.matchAll(EXPR_ATTR_RE)) {\n    out[match[1]!] = match[2]!;\n  }\n  return out;\n}\n\n/**\n * Strip expr markers and collect (type, label) pairs, in document order.\n *\n * The generic {@link extractAndStrip} pass can't produce the right ExpressiveTag for expr\n * (its type would be the literal tag name `expr` and its value the first quoted attribute,\n * i.e. the marker type), so expr gets this dedicated pre-pass. A prosody wrapper's inner\n * words stay in the clean text — only the delimiters are removed — which also keeps\n * streaming safe when an open/close pair is split across chunks.\n */\nfunction splitExpr(text: string): [string, ExpressiveTag[]] {\n  if (!text.includes('<expr') && !text.includes('</expr')) {\n    return [text, []];\n  }\n\n  const tags: ExpressiveTag[] = [];\n\n  let clean = replaceWithGroups(text, EXPR_OPEN_RE, ({ groups, match, offset, source }) => {\n    const attrs = exprAttrs(groups.attrs ?? '');\n    tags.push({ type: attrs.type ?? '', value: attrs.label ?? '' });\n    return dedupRemovalSpace(groups.pre ?? '', '', source, offset + match.length);\n  });\n  clean = replaceWithGroups(clean, EXPR_CLOSE_RE, ({ groups, match, offset, source }) =>\n    dedupRemovalSpace(groups.pre ?? '', '', source, offset + match.length),\n  );\n  return [clean, tags];\n}\n\n/**\n * Lower expr markers to the framework-standard / native tags for `provider`.\n *\n * The output still flows through the existing per-provider conversions in\n * {@link convertMarkup} (e.g. `<sound value=\"X\"/>` -> `[X]` for Inworld/xAI), so this only\n * has to translate expr into those intermediate tags. A type the provider doesn't support\n * (its instructions never advertise it, so it's a hallucination) is dropped from the audio\n * path — the words survive, the marker never leaks.\n */\nfunction convertExpr(provider: string, text: string): string {\n  if (!text.includes('<expr') && !text.includes('</expr')) {\n    return text;\n  }\n\n  const wrap = (attrsRaw: string, inner: string): string => {\n    const attrs = exprAttrs(attrsRaw);\n    const markerType = attrs.type ?? '';\n    const label = (attrs.label ?? '').trim().toLowerCase();\n    if (markerType === 'spell') {\n      return provider === 'cartesia' ? `<spell>${inner}</spell>` : inner;\n    }\n    // prosody: native wrapping tags exist only for xAI\n    if (provider === 'xai') {\n      const native = label.replace(/ /g, '-');\n      if (XAI_WRAPPING.includes(native)) {\n        return `<${native}>${inner}</${native}>`;\n      }\n      return inner;\n    }\n    if (provider === 'inworld') {\n      // not advertised for Inworld; salvage a stray one as a delivery hint\n      return `<expression value=\"${label}\"/>${inner}`;\n    }\n    if (provider === 'cartesia') {\n      // wrapping form of the point controls: apply before the span\n      return (CARTESIA_PROSODY[label] ?? '') + inner;\n    }\n    if (provider === 'fishaudio') {\n      if (label === 'emphasis') {\n        return `<emphasis>${inner}</emphasis>`;\n      }\n      // tone controls are prefix markers: [whispering] steers the words after it\n      if (FISHAUDIO_TONES.includes(label)) {\n        return `[${label}] ${inner}`;\n      }\n      return inner;\n    }\n    return inner;\n  };\n\n  // a marker the provider doesn't support lowers to \"\" — dedupRemovalSpace keeps its\n  // removal from leaving two spaces behind (this text is the transcript when\n  // useTtsAlignedTranscript is on)\n  let out = replaceWithGroups(text, EXPR_WRAP_RE, ({ groups, match, offset, source }) =>\n    dedupRemovalSpace(\n      groups.pre ?? '',\n      wrap(groups.attrs ?? '', groups.inner ?? ''),\n      source,\n      offset + match.length,\n    ),\n  );\n\n  const self = (attrsRaw: string): string => {\n    const attrs = exprAttrs(attrsRaw);\n    const markerType = attrs.type ?? '';\n    let label = attrs.label ?? '';\n    if (markerType === 'expression') {\n      if (provider === 'cartesia') {\n        // Cartesia's discrete emotion vocabulary (instructions list it)\n        return `<emotion value=\"${label}\"/>`;\n      }\n      if (provider === 'inworld' || provider === 'fishaudio') {\n        return `<expression value=\"${label}\"/>`;\n      }\n      return ''; // xAI has no free-form delivery descriptions\n    }\n    if (markerType === 'sound') {\n      if (provider === 'cartesia') {\n        return ''; // no non-verbal sound support\n      }\n      if (provider === 'xai') {\n        label = XAI_SOUND_ALIASES[label.toLowerCase()] ?? label;\n      }\n      if (provider === 'fishaudio') {\n        label = FISHAUDIO_SOUND_ALIASES[label.toLowerCase()] ?? label;\n      }\n      return `<sound value=\"${label}\"/>`;\n    }\n    if (markerType === 'break') {\n      return `<break time=\"${label}\"/>`;\n    }\n    if (markerType === 'prosody' && provider === 'cartesia') {\n      // Cartesia prosody is a self-closing point control (speed/volume)\n      return CARTESIA_PROSODY[label.trim().toLowerCase()] ?? '';\n    }\n    if (markerType === 'prosody' && provider === 'fishaudio') {\n      // tones are taught as wrapping, but Fish's native form is a prefix marker anyway\n      // — salvage a self-closing one as-is\n      const tone = label.trim().toLowerCase();\n      return FISHAUDIO_TONES.includes(tone) ? `[${tone}]` : '';\n    }\n    return '';\n  };\n\n  out = replaceWithGroups(out, EXPR_SELF_RE, ({ groups, match, offset, source }) =>\n    dedupRemovalSpace(groups.pre ?? '', self(groups.attrs ?? ''), source, offset + match.length),\n  );\n  // a stray unpaired expr tag (e.g. a prosody wrapper split across stream chunks)\n  // must never reach the TTS as literal text — drop the delimiters, keep the words\n  out = replaceWithGroups(out, EXPR_OPEN_RE, ({ groups, match, offset, source }) =>\n    dedupRemovalSpace(groups.pre ?? '', '', source, offset + match.length),\n  );\n  out = replaceWithGroups(out, EXPR_CLOSE_RE, ({ groups, match, offset, source }) =>\n    dedupRemovalSpace(groups.pre ?? '', '', source, offset + match.length),\n  );\n  return out;\n}\n\n// Providers with an expr instruction block. Kept as a set so \"does this voice speak\n// markup?\" is answerable without rendering the block — the answer is needed on the\n// per-segment speech path, and the blocks run to several kilobytes.\nconst MARKUP_DIALECTS = new Set(['cartesia', 'inworld', 'xai', 'fishaudio']);\n\n/**\n * Whether `provider` has an expr instruction block, i.e. whether expressive can do\n * anything for it. Allocation-free: prefer this over testing\n * `llmInstructions(...) !== undefined`.\n */\nexport function hasMarkupDialect(provider: string): boolean {\n  return MARKUP_DIALECTS.has(provider);\n}\n\n/**\n * LLM instruction text for a TTS provider, or `undefined` when it has no markup dialect.\n *\n * Each markup-capable provider gets its own expr instruction block — shared marker syntax,\n * but only the types and label vocabularies that provider actually supports;\n * {@link convertMarkup} lowers the markers to native syntax. Expr is the only dialect the\n * LLM is ever taught. When `steering` disables a non-verbal sound, its labels (and any\n * example demonstrating them) are omitted from the block entirely rather than advertised\n * and then revoked.\n */\nexport function llmInstructions(\n  provider: string,\n  steering?: SpeechSteeringOptions,\n): string | undefined {\n  if (!hasMarkupDialect(provider)) {\n    return undefined;\n  }\n  if (provider === 'cartesia') {\n    return CARTESIA_EXPR_LLM_INSTRUCTIONS;\n  }\n  if (provider === 'inworld') {\n    return inworldExprLlmInstructions(allowedSounds(provider, steering));\n  }\n  if (provider === 'xai') {\n    return xaiExprLlmInstructions(\n      allowedSounds(provider, steering),\n      allowedProsody(provider, steering),\n    );\n  }\n  if (provider === 'fishaudio') {\n    return fishaudioExprLlmInstructions(\n      allowedSounds(provider, steering),\n      steering?.disfluencies ?? true,\n    );\n  }\n  return undefined;\n}\n\n// Per-provider native XML tag names. Membership also marks a provider as markup-capable\n// (see normalizeMarkup / convertMarkup); the LLM only ever writes expr markers, so these\n// names exist to lower expr onto and to catch hallucinated natives.\nconst PROVIDER_MARKUP: Record<string, string[]> = {\n  cartesia: CARTESIA_TAGS,\n  inworld: INWORLD_TAGS,\n  xai: XAI_TAGS,\n  // fish's native dialect is square brackets, produced only by convertMarkup for the TTS;\n  // these names exist to catch hallucinated XML natives in transcripts\n  fishaudio: FISHAUDIO_TAGS,\n};\n\n// Union of every provider's XML tag names — used by the transcript sinks to strip markup\n// without knowing which provider produced it (see TranscriptMarkupStripper).\nconst ALL_MARKUP_TAGS: string[] = [...new Set(Object.values(PROVIDER_MARKUP).flat())].sort();\n\n// Tags whose payload lives in an attribute rather than in their content. They are\n// self-closing by definition, but models do write `<expression value=\"warm\">words</expression>`\n// (which is exactly why `normalizeMarkup` repairs that shape) — and the transcript sinks\n// strip the raw text, before any repair. Without this, the wrapped sentence would be\n// recorded as the delivery label and published as `lk.expression`.\nconst ATTRIBUTE_MARKUP_TAGS: ReadonlySet<string> = new Set([\n  'expression',\n  'emotion',\n  'sound',\n  'break',\n  'speed',\n  'volume',\n]);\n\n/**\n * Strip the union of every provider's expressive XML markup (provider-agnostic).\n *\n * The transcript sinks strip downstream, where the originating TTS/provider is no longer\n * in scope, so they remove every provider's XML tags at once: expr markers (all the LLM is\n * ever taught) plus every native tag name, so a hallucinated native tag is stripped rather\n * than leaked.\n *\n * Square-bracket spans are *not* stripped: the LLM only writes expr, so brackets in its\n * output are prose (a `[text](url)` link) that a strip would mangle. Provider-native\n * brackets never arrive here — {@link dropBracketCues} removes them at their source.\n */\nexport function splitAllMarkup(text: string): [string, ExpressiveTag[]] {\n  // every markup shape is angle-bracketed, so text without \"<\" cannot contain any. The\n  // sinks call this per streamed chunk and expressive is off by default, making this the\n  // overwhelmingly common case — skip the tag-union scan entirely\n  if (!text.includes('<')) {\n    return [text, []];\n  }\n\n  const [withoutExpr, exprTags] = splitExpr(text);\n  const [clean, rawTags] = extractAndStrip(withoutExpr, ALL_MARKUP_TAGS, ATTRIBUTE_MARKUP_TAGS);\n  return [clean, [...exprTags, ...rawTags.map(([type, value]) => ({ type, value }))]];\n}\n\n/** {@link splitAllMarkup} returning only the clean text (tags discarded). */\nexport function stripAllMarkup(text: string): string {\n  return splitAllMarkup(text)[0];\n}\n\n/**\n * Strip only the `<expr/>` dialect, leaving all other markup untouched.\n *\n * Unlike {@link stripAllMarkup}, provider-native tags survive (both leave square-bracket\n * spans alone).\n */\nexport function stripExprMarkup(text: string): string {\n  return splitExpr(text)[0];\n}\n\n/**\n * Build the `lk.expression` transcription attribute from stripped markup tags.\n *\n * Surfaces a segment's leading delivery/emotion (`expression` for Inworld/xAI, `emotion`\n * for Cartesia) as `{\"expression\": ..., \"mood\": ...}`: the provider's own words, plus the\n * mood they normalize to, so a client can drive UI off a fixed enum without\n * reimplementing the matching. Returns `undefined` when no such tag was present.\n */\nexport function expressionAttribute(tags: ExpressiveTag[]): Record<string, string> | undefined {\n  const expression = tags.find((t) => t.type === 'expression' || t.type === 'emotion')?.value;\n  if (expression === undefined) {\n    return undefined;\n  }\n  const payload = { expression, mood: matchMood(expression) };\n  return { [ATTRIBUTE_TRANSCRIPTION_EXPRESSION]: JSON.stringify(payload) };\n}\n\n/**\n * Stateful, provider-agnostic markup stripper for one transcript segment.\n *\n * Fed text chunk-by-chunk, it returns the user-visible text and accumulates the stripped\n * tags. A tag-shaped trailing fragment (a partial `<...` arriving split across chunks) is\n * held back until it closes, so a tag straddling a chunk boundary is never emitted\n * half-stripped. Shared by the transcript sinks (room output + transcript synchronizer) so\n * stripping and expression extraction stay identical across them.\n */\nexport class TranscriptMarkupStripper {\n  #buf = '';\n  #tags: ExpressiveTag[] = [];\n  #seamAfterStrip = false;\n  #emittedVisible = false;\n\n  /**\n   * Strip `text`, record its tags, and keep a removed tag from doubling a space.\n   *\n   * {@link splitAllMarkup} drops one of the two spaces a removed tag sat between, but only\n   * when it can see both. Trailing whitespace is therefore held back rather than emitted,\n   * so a tag opening the *next* chunk is still stripped against the space before it;\n   * `final` releases the held whitespace at segment end.\n   */\n  #consume(text: string, final: boolean): string {\n    let input = text;\n    if (this.#seamAfterStrip && (input[0] === ' ' || input[0] === '\\t')) {\n      // a tag was stripped right at the held whitespace: collapse that whitespace with the\n      // run following it, leaving the single separator the words need\n      input = input[0] + input.slice(1).replace(/^[ \\t]+/, '');\n    }\n\n    const [clean, tags] = splitAllMarkup(input);\n    this.#tags.push(...tags);\n\n    const trimmed = trimEndSpaces(clean);\n    const held = final ? '' : clean.slice(trimmed.length);\n    this.#buf = held;\n    // the held whitespace only abuts a removal when this chunk *ended* on a tag; a tag\n    // stripped earlier in the chunk leaves whitespace the LLM itself wrote, which is\n    // passed through rather than collapsed\n    this.#seamAfterStrip = tags.length > 0 && held.length > 0 && trimEndSpaces(input).endsWith('>');\n\n    let emit = clean.slice(0, clean.length - held.length);\n    if (!this.#emittedVisible) {\n      // A marker opening the segment leaves the space that followed it behind: the dedup\n      // drops the whitespace *before* a removed tag, and at position 0 there is none. The\n      // instructions ask for a leading expression marker, so this is the common case —\n      // without this the transcript would open with a space on nearly every turn.\n      emit = emit.replace(/^\\s+/, '');\n    }\n    if (emit) this.#emittedVisible = true;\n    return emit;\n  }\n\n  #hasOpenTag(): boolean {\n    // hold a tag-shaped trailing \"<\" (partial XML tag) so \"3 < 5\" isn't stalled. An\n    // unclosed \"[\" is not held: brackets aren't markup here, and stalling on one would\n    // delay every markdown link until its \"]\" arrives\n    const lastLt = this.#buf.lastIndexOf('<');\n    if (lastLt > this.#buf.lastIndexOf('>')) {\n      const nxt = this.#buf.slice(lastLt + 1, lastLt + 2);\n      if (nxt === '' || nxt === '/' || /[a-z]/i.test(nxt)) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /** Feed a chunk; return the clean text ready to emit (may be empty). */\n  push(text: string): string {\n    this.#buf += text;\n    if (this.#hasOpenTag()) {\n      return '';\n    }\n    return this.#consume(this.#buf, false);\n  }\n\n  /** Drain any buffered text at segment end; return the remaining clean text. */\n  flush(): string {\n    if (!this.#buf) {\n      return '';\n    }\n    return this.#consume(this.#buf, true);\n  }\n\n  /** The markup tags stripped so far, in document order. */\n  get tags(): ExpressiveTag[] {\n    return this.#tags;\n  }\n\n  /** The `lk.expression` attribute for the tags stripped so far, if any. */\n  expressionAttribute(): Record<string, string> | undefined {\n    return expressionAttribute(this.#tags);\n  }\n}\n\nfunction trimEndSpaces(value: string): string {\n  return value.replace(/[ \\t]+$/, '');\n}\n\nconst BRACKET_SPAN_RE = /\\[[^\\]]*\\]/g;\n// cap on how long an unclosed \"[\" is held before it is released as plain text\nconst MAX_HELD_CHARS = 256;\n\n/** A copy of `token` carrying `text`, keeping the alignment metadata. */\nfunction retext(token: TimedString, text: string): TimedString {\n  return createTimedString({\n    text,\n    startTime: token.startTime,\n    endTime: token.endTime,\n    confidence: token.confidence,\n    startTimeOffset: token.startTimeOffset,\n    speakerId: token.speakerId,\n  });\n}\n\n/**\n * Remove bracket cues from TTS-aligned tokens, keeping the survivors' timings.\n *\n * `useTtsAlignedTranscript` makes the provider's alignment of the text it was sent the\n * transcript, and that text is post-{@link convertMarkup}, so it carries native\n * `[laugh]`/`[speak calmly]` cues as words the agent never spoke. Every bracket span goes:\n * the provider reads them all as cues, so none is ever audio, and markdown links are\n * already gone (`filterMarkdown` runs on TTS input by default).\n *\n * Alignment arrives in messages finer-grained than a cue — often one word at a time — so\n * `held` carries the tail of an unclosed span across calls; pass the same array every time\n * and call once more with `final: true` at end of stream to release it.\n */\nexport function dropBracketCues(\n  tokens: TimedString[],\n  held: TimedString[],\n  options: { final?: boolean } = {},\n): TimedString[] {\n  const all = [...held, ...tokens];\n  held.length = 0;\n  const text = all.map((t) => t.text).join('');\n  if (!text.includes('[')) {\n    return all;\n  }\n\n  const dropped = new Set<number>();\n  for (const match of text.matchAll(BRACKET_SPAN_RE)) {\n    let start = match.index!;\n    let end = start + match[0].length;\n    // take one of the spaces the cue sat between, so it leaves a single separator\n    if (start > 0 && text[start - 1] === ' ' && (end === text.length || text[end] === ' ')) {\n      start -= 1;\n    } else if (start === 0 && end < text.length && text[end] === ' ') {\n      end += 1;\n    }\n    for (let i = start; i < end; i++) dropped.add(i);\n  }\n\n  // hold from an unclosed \"[\" so a cue straddling messages is still judged as a whole;\n  // past MAX_HELD_CHARS give up, since a lone bracket must not stall the transcript\n  let holdFrom = text.length;\n  if (!options.final) {\n    const openAt = text.lastIndexOf('[');\n    if (openAt > text.lastIndexOf(']') && text.length - openAt <= MAX_HELD_CHARS) {\n      holdFrom = openAt;\n    }\n  }\n\n  const out: TimedString[] = [];\n  let pos = 0;\n  for (const token of all) {\n    let emit = '';\n    let keep = '';\n    for (let i = 0; i < token.text.length; i++) {\n      const idx = pos + i;\n      const char = token.text[i]!;\n      if (idx >= holdFrom) {\n        keep += char;\n      } else if (!dropped.has(idx)) {\n        emit += char;\n      }\n    }\n    pos += token.text.length;\n    if (emit) out.push(emit === token.text ? token : retext(token, emit));\n    if (keep) held.push(keep === token.text ? token : retext(token, keep));\n  }\n  return out;\n}\n\nconst SELF_CLOSING_TAGS: Record<string, string[]> = {\n  cartesia: ['emotion', 'speed', 'volume', 'break'],\n  inworld: ['expression', 'sound', 'break'],\n  fishaudio: ['expression', 'sound', 'break'],\n};\n\n/**\n * Fix common LLM markup mistakes for a provider.\n *\n * Closes opening tags that should be self-closing (e.g. the LLM writes\n * `<expression value=\"happy\">` instead of `<expression value=\"happy\"/>` — or\n * `<expr type=\"sound\" label=\"laugh\">` instead of `<expr type=\"sound\" label=\"laugh\"/>`).\n */\nexport function normalizeMarkup(provider: string, text: string): string {\n  let out = text;\n  if (provider in PROVIDER_MARKUP) {\n    out = out.replace(EXPR_UNCLOSED_RE, '$1/>');\n  }\n  const tags = SELF_CLOSING_TAGS[provider];\n  if (!tags) {\n    return out;\n  }\n  const pattern = new RegExp(`<(${tags.map(escapeRegExp).join('|')})\\\\b([^>]*[^/])\\\\s*>`, 'g');\n  return out.replace(pattern, '<$1$2/>');\n}\n\n/** Convert framework-standard markup to a provider's native syntax. */\nexport function convertMarkup(provider: string, text: string): string {\n  let out = text;\n  if (provider in PROVIDER_MARKUP) {\n    // lower expr markers first; the per-provider conversions below then handle the\n    // intermediate framework-standard tags they produce\n    out = convertExpr(provider, out);\n  }\n  if (provider === 'inworld' || provider === 'xai') {\n    // <sound value=\"X\"/> -> [X] (and <expression value=\"X\"/> -> [X]); for xAI this turns\n    // inline sounds into its native brackets while emotion/prosody stay <..>\n    out = convertExpressionTags(out);\n  }\n  if (provider === 'xai') {\n    // xAI has no <break>; map it to its native [pause]/[long-pause]\n    out = out.replace(XAI_BREAK_RE, xaiBreakToBracket);\n  }\n  if (provider === 'fishaudio') {\n    // <expression value=\"X\"/> -> [very X] first (the intensified form steers harder),\n    // then the generic pass lowers the remaining <sound value=\"X\"/> -> [X]\n    out = out.replace(FISHAUDIO_EXPRESSION_RE, fishaudioExpressionToBracket);\n    out = convertExpressionTags(out);\n    out = out.replace(FISHAUDIO_BREAK_RE, fishaudioBreakToBracket);\n    // Fish's per-word stress marker: <emphasis>word</emphasis> -> [emphasis] word\n    out = out.replace(FISHAUDIO_EMPHASIS_RE, (_m, inner: string) => `[emphasis] ${inner.trim()}`);\n  }\n  // <break> is otherwise passed through unchanged: Inworld accepts it as native SSML.\n  return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,uBAAmD;AACnD,sBAAuC;AAEvC,gBAAoD;AACpD,0BAOO;AACP,kBAA0B;AA4DnB,MAAM,kCAAyD,EAAE,cAAc,KAAK;AAU3F,MAAM,gBAAgB,CAAC,WAAW,SAAS,UAAU,SAAS,OAAO;AAErE,MAAM,eAAe,CAAC,cAAc,SAAS,OAAO;AAYpD,MAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,eAAe;AAAA,EACnB;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AACA,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIA,MAAM,WAAW,CAAC,GAAG,cAAc,GAAG,cAAc,SAAS,OAAO;AAIpE,MAAM,eAAe;AAErB,SAAS,qBAAqB,KAAqB;AACjD,QAAM,QAAQ,IAAI,KAAK,EAAE,YAAY;AACrC,QAAM,SAAS,MAAM,SAAS,IAAI,IAC9B,OAAO,WAAW,MAAM,MAAM,GAAG,EAAE,CAAC,IAAI,MACxC,OAAO,WAAW,MAAM,QAAQ,OAAO,EAAE,CAAC;AAC9C,SAAO,OAAO,MAAM,MAAM,IAAI,IAAI;AACpC;AAEA,SAAS,kBAAkB,QAAgB,MAAsB;AAC/D,SAAO,qBAAqB,IAAI,KAAK,IAAI,iBAAiB;AAC5D;AAgBA,MAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIA,MAAM,kBAAkB,CAAC,cAAc,QAAQ,YAAY,SAAS;AACpE,MAAM,iBAAiB,CAAC,cAAc,SAAS,SAAS,UAAU;AAElE,MAAM,0BAA0B;AAChC,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;AAE9B,SAAS,6BAA6B,QAAgB,OAAuB;AAG3E,MAAI,QAAQ,MAAM,KAAK;AACvB,MAAI,SAAS,CAAC,MAAM,YAAY,EAAE,WAAW,OAAO,GAAG;AACrD,YAAQ,QAAQ,KAAK;AAAA,EACvB;AACA,SAAO,IAAI,KAAK;AAClB;AAEA,SAAS,wBAAwB,QAAgB,MAAsB;AAErE,SAAO,qBAAqB,IAAI,KAAK,IAAI,iBAAiB;AAC5D;AA6BA,MAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAkBtB,MAAM,iCACJ,gBACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCF,MAAM,iBAAiB,CAAC,SAAS,QAAQ,WAAW,gBAAgB,SAAS,MAAM;AAEnF,MAAM,mBAAmB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AACF;AAGA,SAAS,cAAc,UAAoB,SAAmB,YAAgC;AAC5F,QAAM,UAAU,WAAW,OAAO,CAAC,MAAM,CAAC,QAAQ,SAAS,CAAC,CAAC;AAC7D,SAAO,SAAS,OAAO,CAAC,OAAO,CAAC,QAAQ,KAAK,CAAC,MAAM,GAAG,SAAS,UAAU,CAAC,GAAG,CAAC,CAAC;AAClF;AAEA,SAAS,iBAAiB,UAA4B;AACpD,SAAO,SAAS,IAAI,CAAC,SAAS,MAAM,GAAG,IAAI,CAAC,KAAK,OAAO,EAAE,EAAE,KAAK,MAAM;AACzE;AAEA,SAAS,2BAA2B,QAA0B;AAC5D,QAAM,WAAW;AAAA,IACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+CF;AAEA,MAAI,OAAO,QAAQ;AACjB,UAAM,OAAO;AACb,QAAI,UAAU;AAAA,+BACa,OAAO,CAAC,CAAC;AAAA,oCACJ,OAAO,KAAK,IAAI,CAAC;AAAA,6HAEnB,OAAO,SAAS,cAAc,IAAI,OAAO,EAAE;AAEzE,QAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,iBAAW;AAAA;AAAA,IAIb;AACA,aAAS,KAAK,OAAO;AAAA,EACvB;AAEA,WAAS,KAAK;AAAA;AAAA;AAAA,oRAOH;AAEX,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,iBAAiB,QAAQ;AAAA,IACzB;AAAA,IAEA;AAAA,IAIA;AAAA,EAGF;AAEA,MAAI,OAAO,SAAS,OAAO,GAAG;AAC5B,UAAM;AAAA,MACJ;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,WAAW,cAAc,kBAAkB,QAAQ,cAAc;AACvE,MAAI,SAAS,QAAQ;AACnB,UAAM,KAAK,gBAAgB,SAAS,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACvE;AACA,SAAO,MAAM,KAAK,MAAM;AAC1B;AAEA,MAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AACF;AAEA,SAAS,uBAAuB,QAAkB,SAA2B;AAC3E,QAAM,WAAqB,CAAC;AAC5B,MAAI,OAAO,QAAQ;AACjB,aAAS,KAAK;AAAA,+BACa,OAAO,CAAC,CAAC;AAAA,oCACJ,OAAO,KAAK,IAAI,CAAC;AAAA,wKAEwB;AAAA,EAC3E;AAEA,WAAS,KAAK;AAAA;AAAA,mTAKgC;AAE9C,QAAM,QAAQ,QAAQ,OAAO,CAAC,MAAM,MAAM,UAAU;AACpD,WAAS,KAAK;AAAA;AAAA,oCAEoB,MAAM,KAAK,IAAI,CAAC;AAAA,uKAE4B;AAE9E,WAAS,KAAK;AAAA;AAAA,uKAG4D;AAE1E,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,iBAAiB,QAAQ;AAAA,IACzB,+FACG,OAAO,SAAS,oCAAoC,6BACrD;AAAA,IACF;AAAA,IAIA;AAAA,IAEA;AAAA,EAIF;AAKA,QAAM,WAAW;AAAA,IACf;AAAA,EAEF;AACA,MAAI,CAAC,SAAS,WAAW,QAAQ,EAAE,KAAK,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC,GAAG;AAClE,aAAS;AAAA,MACP;AAAA,IAIF;AAAA,EACF;AACA,QAAM,KAAK,SAAS,KAAK,GAAG,CAAC;AAE7B,QAAM,WAAW;AAAA,IACf;AAAA,IACA,CAAC,GAAG,QAAQ,GAAG,OAAO;AAAA,IACtB,CAAC,GAAG,YAAY,GAAG,YAAY;AAAA,EACjC;AACA,MAAI,SAAS,QAAQ;AACnB,UAAM,KAAK,gBAAgB,SAAS,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACvE;AACA,SAAO,MAAM,KAAK,MAAM;AAC1B;AAMA,MAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AACF;AAKA,MAAM,+BAA+B;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,6BAA6B,QAAkB,eAAe,MAAc;AACnF,QAAM,WAAW;AAAA,IACf;AAAA;AAAA,gEAE4D,mBAAmB,KAAK,IAAI,CAAC;AAAA;AAAA,EAG3F;AAEA,MAAI,OAAO,QAAQ;AACjB,aAAS,KAAK;AAAA,+BACa,OAAO,CAAC,CAAC;AAAA,oCACJ,OAAO,KAAK,IAAI,CAAC;AAAA,wKAEwB;AAAA,EAC3E;AAEA,WAAS,KAAK;AAAA;AAAA,mTAKgC;AAE9C,WAAS,KAAK;AAAA;AAAA,oCAEoB,gBAAgB,KAAK,IAAI,CAAC;AAAA,2JAEQ;AAEpE,WAAS,KAAK;AAAA;AAAA,wFAEwE;AAEtF,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,iBAAiB,QAAQ;AAAA,IACzB;AAAA,IAIA;AAAA,EAEF;AAKA,QAAM,WAAW;AAAA,IACf;AAAA,IAGA;AAAA,EAEF;AACA,MAAI,CAAC,YAAY,WAAW,EAAE,KAAK,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC,GAAG;AAC7D,aAAS;AAAA,MACP;AAAA,IAEF;AAAA,EACF;AACA,MAAI,cAAc;AAChB,aAAS;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,SAAS,KAAK,GAAG,CAAC;AAE7B,QAAM,OAAO,CAAC,GAAG,oBAAoB,GAAI,eAAe,+BAA+B,CAAC,CAAE;AAC1F,QAAM,WAAW,cAAc,MAAM,QAAQ,gBAAgB;AAC7D,MAAI,SAAS,QAAQ;AACnB,UAAM,KAAK,gBAAgB,SAAS,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACvE;AACA,SAAO,MAAM,KAAK,MAAM;AAC1B;AAIA,MAAM,kBAA4C;AAAA,EAChD,SAAS;AAAA,EACT,KAAK;AAAA,EACL,WAAW;AACb;AAYA,SAAS,gBACP,OACA,UACA,UACa;AACb,QAAM,aAAa,qCAAU;AAC7B,QAAM,SAAS,MAAM,QAAQ;AAC7B,MAAI,eAAe,UAAa,eAAe,QAAQ,WAAW,QAAW;AAC3E,WAAO,oBAAI,IAAI;AAAA,EACjB;AACA,MAAI,eAAe,OAAO;AACxB,WAAO,IAAI,IAAI,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC;AAAA,EAC7C;AACA,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,CAAC,OAAO,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAmC;AACzF,QAAI,WAAW,KAAK,MAAM,OAAO;AAC/B,iBAAW,SAAS,YAAa,SAAQ,IAAI,KAAK;AAAA,IACpD;AAAA,EACF;AACA,SAAO;AACT;AASA,SAAS,cAAc,UAAkB,UAAuD;AAC9F,QAAM,UAAU,gBAAgB,wBAAwB,UAAU,QAAQ;AAC1E,UAAQ,gBAAgB,QAAQ,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACxE;AASA,SAAS,eAAe,UAAkB,UAAuD;AAC/F,QAAM,UAAU,gBAAgB,0BAA0B,UAAU,QAAQ;AAC5E,UAAQ,iBAAiB,QAAQ,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AACzE;AAQA,MAAM,yBAAyC;AAAA,EAC7C,SAAS;AAAA,IACP,UAAU,CAAC,OAAO;AAAA,IAClB,WAAW,CAAC,SAAS;AAAA,IACrB,SAAS,CAAC,MAAM;AAAA,IAChB,QAAQ,CAAC;AAAA,IACT,YAAY,CAAC;AAAA,IACb,aAAa,CAAC;AAAA,IACd,cAAc,CAAC,SAAS,gBAAgB,MAAM;AAAA,EAChD;AAAA,EACA,KAAK;AAAA,IACH,UAAU,CAAC,SAAS,WAAW,QAAQ;AAAA,IACvC,WAAW,CAAC,UAAU,UAAU,QAAQ;AAAA,IACxC,SAAS,CAAC,MAAM;AAAA,IAChB,QAAQ,CAAC,KAAK;AAAA,IACd,YAAY,CAAC,UAAU;AAAA;AAAA,IACvB,aAAa,CAAC,OAAO,gBAAgB,WAAW;AAAA,IAChD,cAAc,CAAC;AAAA;AAAA,EACjB;AAAA,EACA,WAAW;AAAA,IACT,UAAU,CAAC,YAAY,WAAW;AAAA,IAClC,WAAW,CAAC,SAAS;AAAA,IACrB,SAAS,CAAC,SAAS;AAAA,IACnB,QAAQ,CAAC,SAAS;AAAA,IAClB,YAAY,CAAC,UAAU;AAAA,IACvB,aAAa,CAAC;AAAA,IACd,cAAc,CAAC,gBAAgB,SAAS;AAAA,EAC1C;AACF;AAKA,MAAM,2BAA2C;AAAA,EAC/C,KAAK;AAAA,IACH,UAAU,CAAC,aAAa;AAAA,IACxB,YAAY,CAAC,aAAa,SAAS;AAAA,EACrC;AACF;AAGA,MAAM,mBAA6C;AAAA,EACjD,KAAK;AACP;AAGO,SAAS,oBAAoB,UAA6D;AAC/F,QAAM,SAAoD,CAAC;AAC3D,aAAW,SAAS,CAAC,wBAAwB,wBAAwB,GAAG;AACtE,eAAW,CAAC,OAAO,MAAM,KAAK,OAAO,QAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC,GAG7D;AACH,UAAI,OAAO,QAAQ;AACjB,eAAO,KAAK,IAAI,CAAC,GAAI,OAAO,KAAK,KAAK,CAAC,GAAI,GAAG,MAAM;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOA,MAAM,oBAA4C;AAAA,EAChD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,KAAK;AAAA,EACL,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AACX;AAGA,SAAS,cAAc,QAA0B;AAC/C,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,kBAAkB,KAAK;AACpC,QAAI,QAAQ,CAAC,MAAM,SAAS,IAAI,EAAG,OAAM,KAAK,IAAI;AAAA,EACpD;AACA,MAAI,OAAO;AACX,MAAI,MAAM,OAAQ,SAAQ,aAAQ,MAAM,KAAK,IAAI;AACjD,SAAO,OAAO;AAChB;AAUO,SAAS,qBAAqB,UAAkB,UAAyC;AAC9F,QAAM,QAAkB,CAAC;AAKzB,MAAI,gBAAgB,wBAAwB,UAAU,QAAQ,EAAE,MAAM;AACpE,UAAM,UAAU,cAAc,UAAU,QAAQ;AAChD,QAAI,QAAQ,OAAQ,OAAM,KAAK,cAAc,OAAO,CAAC;AAAA,EACvD;AAEA,MAAI,SAAS,iBAAiB,QAAW;AACvC,UAAM;AAAA,MACJ,SAAS,eACL,6GAEA;AAAA,IACN;AAAA,EACF;AAEA,MAAI,SAAS,SAAS,UAAa,SAAS,SAAS,UAAU;AAC7D,UAAM,KAAK,UAAU,SAAS,IAAI,yBAAyB;AAAA,EAC7D;AAEA,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO,2BAA2B,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,EAAE,KAAK,IAAI;AAC9E;AAMA,MAAM,gBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,UAAU;AAAA;AAAA;AAAA,EAGV,KAAK;AAAA;AAAA;AAAA;AAIP;AAGO,SAAS,YAAY,UAAsC;AAChE,SAAO,cAAc,QAAQ;AAC/B;AAYA,MAAM,uBAAuB;AAS7B,MAAM,6BAA6B;AAY5B,SAAS,kBACd,UACA,SACmB;AACnB,QAAM,SAAS,cAAc,QAAQ;AACrC,QAAM,WAAW,QAAQ,cAAc,WAAW;AAClD,SAAO,IAAI,gBAAAA,MAAc,kBAAkB;AAAA,IACzC,gBAAgB;AAAA;AAAA,IAEhB,gBAAgB,WAAW,KAAK,IAAI,sBAAsB,MAAO,IAAI;AAAA,IACrE,kBAAkB,WAAW,6BAA6B;AAAA;AAAA;AAAA,IAG1D,UAAU,QAAQ;AAAA,EACpB,CAAC;AACH;AAEA,MAAM,eAAe;AAIrB,MAAM,eAAe,IAAI,OAAO,iCAAa,mCAAmC,GAAG;AACnF,MAAM,gBAAgB,IAAI,OAAO,iCAAa,eAAe,GAAG;AAEhE,MAAM,eAAe,IAAI,OAAO,iCAAa,kCAAkC,GAAG;AAOlF,MAAM,eAAe,IAAI;AAAA,EACvB,iCACE;AAAA,EACF;AACF;AAEA,MAAM,mBAAmB;AAGzB,MAAM,oBAA4C,EAAE,SAAS,SAAS;AAItE,MAAM,0BAAkD;AAAA,EACtD,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AACP;AAGA,MAAM,mBAA2C;AAAA,EAC/C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAEA,SAAS,UAAU,OAAuC;AACxD,QAAM,MAA8B,CAAC;AACrC,aAAW,SAAS,MAAM,SAAS,YAAY,GAAG;AAChD,QAAI,MAAM,CAAC,CAAE,IAAI,MAAM,CAAC;AAAA,EAC1B;AACA,SAAO;AACT;AAWA,SAAS,UAAU,MAAyC;AAC1D,MAAI,CAAC,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,SAAS,QAAQ,GAAG;AACvD,WAAO,CAAC,MAAM,CAAC,CAAC;AAAA,EAClB;AAEA,QAAM,OAAwB,CAAC;AAE/B,MAAI,YAAQ,uCAAkB,MAAM,cAAc,CAAC,EAAE,QAAQ,OAAO,QAAQ,OAAO,MAAM;AACvF,UAAM,QAAQ,UAAU,OAAO,SAAS,EAAE;AAC1C,SAAK,KAAK,EAAE,MAAM,MAAM,QAAQ,IAAI,OAAO,MAAM,SAAS,GAAG,CAAC;AAC9D,eAAO,uCAAkB,OAAO,OAAO,IAAI,IAAI,QAAQ,SAAS,MAAM,MAAM;AAAA,EAC9E,CAAC;AACD,cAAQ;AAAA,IAAkB;AAAA,IAAO;AAAA,IAAe,CAAC,EAAE,QAAQ,OAAO,QAAQ,OAAO,UAC/E,uCAAkB,OAAO,OAAO,IAAI,IAAI,QAAQ,SAAS,MAAM,MAAM;AAAA,EACvE;AACA,SAAO,CAAC,OAAO,IAAI;AACrB;AAWA,SAAS,YAAY,UAAkB,MAAsB;AAC3D,MAAI,CAAC,KAAK,SAAS,OAAO,KAAK,CAAC,KAAK,SAAS,QAAQ,GAAG;AACvD,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,CAAC,UAAkB,UAA0B;AACxD,UAAM,QAAQ,UAAU,QAAQ;AAChC,UAAM,aAAa,MAAM,QAAQ;AACjC,UAAM,SAAS,MAAM,SAAS,IAAI,KAAK,EAAE,YAAY;AACrD,QAAI,eAAe,SAAS;AAC1B,aAAO,aAAa,aAAa,UAAU,KAAK,aAAa;AAAA,IAC/D;AAEA,QAAI,aAAa,OAAO;AACtB,YAAM,SAAS,MAAM,QAAQ,MAAM,GAAG;AACtC,UAAI,aAAa,SAAS,MAAM,GAAG;AACjC,eAAO,IAAI,MAAM,IAAI,KAAK,KAAK,MAAM;AAAA,MACvC;AACA,aAAO;AAAA,IACT;AACA,QAAI,aAAa,WAAW;AAE1B,aAAO,sBAAsB,KAAK,MAAM,KAAK;AAAA,IAC/C;AACA,QAAI,aAAa,YAAY;AAE3B,cAAQ,iBAAiB,KAAK,KAAK,MAAM;AAAA,IAC3C;AACA,QAAI,aAAa,aAAa;AAC5B,UAAI,UAAU,YAAY;AACxB,eAAO,aAAa,KAAK;AAAA,MAC3B;AAEA,UAAI,gBAAgB,SAAS,KAAK,GAAG;AACnC,eAAO,IAAI,KAAK,KAAK,KAAK;AAAA,MAC5B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAKA,MAAI,UAAM;AAAA,IAAkB;AAAA,IAAM;AAAA,IAAc,CAAC,EAAE,QAAQ,OAAO,QAAQ,OAAO,UAC/E;AAAA,MACE,OAAO,OAAO;AAAA,MACd,KAAK,OAAO,SAAS,IAAI,OAAO,SAAS,EAAE;AAAA,MAC3C;AAAA,MACA,SAAS,MAAM;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,OAAO,CAAC,aAA6B;AACzC,UAAM,QAAQ,UAAU,QAAQ;AAChC,UAAM,aAAa,MAAM,QAAQ;AACjC,QAAI,QAAQ,MAAM,SAAS;AAC3B,QAAI,eAAe,cAAc;AAC/B,UAAI,aAAa,YAAY;AAE3B,eAAO,mBAAmB,KAAK;AAAA,MACjC;AACA,UAAI,aAAa,aAAa,aAAa,aAAa;AACtD,eAAO,sBAAsB,KAAK;AAAA,MACpC;AACA,aAAO;AAAA,IACT;AACA,QAAI,eAAe,SAAS;AAC1B,UAAI,aAAa,YAAY;AAC3B,eAAO;AAAA,MACT;AACA,UAAI,aAAa,OAAO;AACtB,gBAAQ,kBAAkB,MAAM,YAAY,CAAC,KAAK;AAAA,MACpD;AACA,UAAI,aAAa,aAAa;AAC5B,gBAAQ,wBAAwB,MAAM,YAAY,CAAC,KAAK;AAAA,MAC1D;AACA,aAAO,iBAAiB,KAAK;AAAA,IAC/B;AACA,QAAI,eAAe,SAAS;AAC1B,aAAO,gBAAgB,KAAK;AAAA,IAC9B;AACA,QAAI,eAAe,aAAa,aAAa,YAAY;AAEvD,aAAO,iBAAiB,MAAM,KAAK,EAAE,YAAY,CAAC,KAAK;AAAA,IACzD;AACA,QAAI,eAAe,aAAa,aAAa,aAAa;AAGxD,YAAM,OAAO,MAAM,KAAK,EAAE,YAAY;AACtC,aAAO,gBAAgB,SAAS,IAAI,IAAI,IAAI,IAAI,MAAM;AAAA,IACxD;AACA,WAAO;AAAA,EACT;AAEA,YAAM;AAAA,IAAkB;AAAA,IAAK;AAAA,IAAc,CAAC,EAAE,QAAQ,OAAO,QAAQ,OAAO,UAC1E,uCAAkB,OAAO,OAAO,IAAI,KAAK,OAAO,SAAS,EAAE,GAAG,QAAQ,SAAS,MAAM,MAAM;AAAA,EAC7F;AAGA,YAAM;AAAA,IAAkB;AAAA,IAAK;AAAA,IAAc,CAAC,EAAE,QAAQ,OAAO,QAAQ,OAAO,UAC1E,uCAAkB,OAAO,OAAO,IAAI,IAAI,QAAQ,SAAS,MAAM,MAAM;AAAA,EACvE;AACA,YAAM;AAAA,IAAkB;AAAA,IAAK;AAAA,IAAe,CAAC,EAAE,QAAQ,OAAO,QAAQ,OAAO,UAC3E,uCAAkB,OAAO,OAAO,IAAI,IAAI,QAAQ,SAAS,MAAM,MAAM;AAAA,EACvE;AACA,SAAO;AACT;AAKA,MAAM,kBAAkB,oBAAI,IAAI,CAAC,YAAY,WAAW,OAAO,WAAW,CAAC;AAOpE,SAAS,iBAAiB,UAA2B;AAC1D,SAAO,gBAAgB,IAAI,QAAQ;AACrC;AAYO,SAAS,gBACd,UACA,UACoB;AACpB,MAAI,CAAC,iBAAiB,QAAQ,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,MAAI,aAAa,YAAY;AAC3B,WAAO;AAAA,EACT;AACA,MAAI,aAAa,WAAW;AAC1B,WAAO,2BAA2B,cAAc,UAAU,QAAQ,CAAC;AAAA,EACrE;AACA,MAAI,aAAa,OAAO;AACtB,WAAO;AAAA,MACL,cAAc,UAAU,QAAQ;AAAA,MAChC,eAAe,UAAU,QAAQ;AAAA,IACnC;AAAA,EACF;AACA,MAAI,aAAa,aAAa;AAC5B,WAAO;AAAA,MACL,cAAc,UAAU,QAAQ;AAAA,OAChC,qCAAU,iBAAgB;AAAA,IAC5B;AAAA,EACF;AACA,SAAO;AACT;AAKA,MAAM,kBAA4C;AAAA,EAChD,UAAU;AAAA,EACV,SAAS;AAAA,EACT,KAAK;AAAA;AAAA;AAAA,EAGL,WAAW;AACb;AAIA,MAAM,kBAA4B,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,eAAe,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK;AAO3F,MAAM,wBAA6C,oBAAI,IAAI;AAAA,EACzD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAcM,SAAS,eAAe,MAAyC;AAItE,MAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACvB,WAAO,CAAC,MAAM,CAAC,CAAC;AAAA,EAClB;AAEA,QAAM,CAAC,aAAa,QAAQ,IAAI,UAAU,IAAI;AAC9C,QAAM,CAAC,OAAO,OAAO,QAAI,qCAAgB,aAAa,iBAAiB,qBAAqB;AAC5F,SAAO,CAAC,OAAO,CAAC,GAAG,UAAU,GAAG,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE,CAAC,CAAC;AACpF;AAGO,SAAS,eAAe,MAAsB;AACnD,SAAO,eAAe,IAAI,EAAE,CAAC;AAC/B;AAQO,SAAS,gBAAgB,MAAsB;AACpD,SAAO,UAAU,IAAI,EAAE,CAAC;AAC1B;AAUO,SAAS,oBAAoB,MAA2D;AAztC/F;AA0tCE,QAAM,cAAa,UAAK,KAAK,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,SAAS,SAAS,MAAhE,mBAAmE;AACtF,MAAI,eAAe,QAAW;AAC5B,WAAO;AAAA,EACT;AACA,QAAM,UAAU,EAAE,YAAY,UAAM,uBAAU,UAAU,EAAE;AAC1D,SAAO,EAAE,CAAC,mDAAkC,GAAG,KAAK,UAAU,OAAO,EAAE;AACzE;AAWO,MAAM,yBAAyB;AAAA,EACpC,OAAO;AAAA,EACP,QAAyB,CAAC;AAAA,EAC1B,kBAAkB;AAAA,EAClB,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUlB,SAAS,MAAc,OAAwB;AAC7C,QAAI,QAAQ;AACZ,QAAI,KAAK,oBAAoB,MAAM,CAAC,MAAM,OAAO,MAAM,CAAC,MAAM,MAAO;AAGnE,cAAQ,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,EAAE,QAAQ,WAAW,EAAE;AAAA,IACzD;AAEA,UAAM,CAAC,OAAO,IAAI,IAAI,eAAe,KAAK;AAC1C,SAAK,MAAM,KAAK,GAAG,IAAI;AAEvB,UAAM,UAAU,cAAc,KAAK;AACnC,UAAM,OAAO,QAAQ,KAAK,MAAM,MAAM,QAAQ,MAAM;AACpD,SAAK,OAAO;AAIZ,SAAK,kBAAkB,KAAK,SAAS,KAAK,KAAK,SAAS,KAAK,cAAc,KAAK,EAAE,SAAS,GAAG;AAE9F,QAAI,OAAO,MAAM,MAAM,GAAG,MAAM,SAAS,KAAK,MAAM;AACpD,QAAI,CAAC,KAAK,iBAAiB;AAKzB,aAAO,KAAK,QAAQ,QAAQ,EAAE;AAAA,IAChC;AACA,QAAI,KAAM,MAAK,kBAAkB;AACjC,WAAO;AAAA,EACT;AAAA,EAEA,cAAuB;AAIrB,UAAM,SAAS,KAAK,KAAK,YAAY,GAAG;AACxC,QAAI,SAAS,KAAK,KAAK,YAAY,GAAG,GAAG;AACvC,YAAM,MAAM,KAAK,KAAK,MAAM,SAAS,GAAG,SAAS,CAAC;AAClD,UAAI,QAAQ,MAAM,QAAQ,OAAO,SAAS,KAAK,GAAG,GAAG;AACnD,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,KAAK,MAAsB;AACzB,SAAK,QAAQ;AACb,QAAI,KAAK,YAAY,GAAG;AACtB,aAAO;AAAA,IACT;AACA,WAAO,KAAK,SAAS,KAAK,MAAM,KAAK;AAAA,EACvC;AAAA;AAAA,EAGA,QAAgB;AACd,QAAI,CAAC,KAAK,MAAM;AACd,aAAO;AAAA,IACT;AACA,WAAO,KAAK,SAAS,KAAK,MAAM,IAAI;AAAA,EACtC;AAAA;AAAA,EAGA,IAAI,OAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,sBAA0D;AACxD,WAAO,oBAAoB,KAAK,KAAK;AAAA,EACvC;AACF;AAEA,SAAS,cAAc,OAAuB;AAC5C,SAAO,MAAM,QAAQ,WAAW,EAAE;AACpC;AAEA,MAAM,kBAAkB;AAExB,MAAM,iBAAiB;AAGvB,SAAS,OAAO,OAAoB,MAA2B;AAC7D,aAAO,6BAAkB;AAAA,IACvB;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,SAAS,MAAM;AAAA,IACf,YAAY,MAAM;AAAA,IAClB,iBAAiB,MAAM;AAAA,IACvB,WAAW,MAAM;AAAA,EACnB,CAAC;AACH;AAeO,SAAS,gBACd,QACA,MACA,UAA+B,CAAC,GACjB;AACf,QAAM,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM;AAC/B,OAAK,SAAS;AACd,QAAM,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE;AAC3C,MAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,SAAS,KAAK,SAAS,eAAe,GAAG;AAClD,QAAI,QAAQ,MAAM;AAClB,QAAI,MAAM,QAAQ,MAAM,CAAC,EAAE;AAE3B,QAAI,QAAQ,KAAK,KAAK,QAAQ,CAAC,MAAM,QAAQ,QAAQ,KAAK,UAAU,KAAK,GAAG,MAAM,MAAM;AACtF,eAAS;AAAA,IACX,WAAW,UAAU,KAAK,MAAM,KAAK,UAAU,KAAK,GAAG,MAAM,KAAK;AAChE,aAAO;AAAA,IACT;AACA,aAAS,IAAI,OAAO,IAAI,KAAK,IAAK,SAAQ,IAAI,CAAC;AAAA,EACjD;AAIA,MAAI,WAAW,KAAK;AACpB,MAAI,CAAC,QAAQ,OAAO;AAClB,UAAM,SAAS,KAAK,YAAY,GAAG;AACnC,QAAI,SAAS,KAAK,YAAY,GAAG,KAAK,KAAK,SAAS,UAAU,gBAAgB;AAC5E,iBAAW;AAAA,IACb;AAAA,EACF;AAEA,QAAM,MAAqB,CAAC;AAC5B,MAAI,MAAM;AACV,aAAW,SAAS,KAAK;AACvB,QAAI,OAAO;AACX,QAAI,OAAO;AACX,aAAS,IAAI,GAAG,IAAI,MAAM,KAAK,QAAQ,KAAK;AAC1C,YAAM,MAAM,MAAM;AAClB,YAAM,OAAO,MAAM,KAAK,CAAC;AACzB,UAAI,OAAO,UAAU;AACnB,gBAAQ;AAAA,MACV,WAAW,CAAC,QAAQ,IAAI,GAAG,GAAG;AAC5B,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,WAAO,MAAM,KAAK;AAClB,QAAI,KAAM,KAAI,KAAK,SAAS,MAAM,OAAO,QAAQ,OAAO,OAAO,IAAI,CAAC;AACpE,QAAI,KAAM,MAAK,KAAK,SAAS,MAAM,OAAO,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAEA,MAAM,oBAA8C;AAAA,EAClD,UAAU,CAAC,WAAW,SAAS,UAAU,OAAO;AAAA,EAChD,SAAS,CAAC,cAAc,SAAS,OAAO;AAAA,EACxC,WAAW,CAAC,cAAc,SAAS,OAAO;AAC5C;AASO,SAAS,gBAAgB,UAAkB,MAAsB;AACtE,MAAI,MAAM;AACV,MAAI,YAAY,iBAAiB;AAC/B,UAAM,IAAI,QAAQ,kBAAkB,MAAM;AAAA,EAC5C;AACA,QAAM,OAAO,kBAAkB,QAAQ;AACvC,MAAI,CAAC,MAAM;AACT,WAAO;AAAA,EACT;AACA,QAAM,UAAU,IAAI,OAAO,KAAK,KAAK,IAAI,gCAAY,EAAE,KAAK,GAAG,CAAC,wBAAwB,GAAG;AAC3F,SAAO,IAAI,QAAQ,SAAS,SAAS;AACvC;AAGO,SAAS,cAAc,UAAkB,MAAsB;AACpE,MAAI,MAAM;AACV,MAAI,YAAY,iBAAiB;AAG/B,UAAM,YAAY,UAAU,GAAG;AAAA,EACjC;AACA,MAAI,aAAa,aAAa,aAAa,OAAO;AAGhD,cAAM,2CAAsB,GAAG;AAAA,EACjC;AACA,MAAI,aAAa,OAAO;AAEtB,UAAM,IAAI,QAAQ,cAAc,iBAAiB;AAAA,EACnD;AACA,MAAI,aAAa,aAAa;AAG5B,UAAM,IAAI,QAAQ,yBAAyB,4BAA4B;AACvE,cAAM,2CAAsB,GAAG;AAC/B,UAAM,IAAI,QAAQ,oBAAoB,uBAAuB;AAE7D,UAAM,IAAI,QAAQ,uBAAuB,CAAC,IAAI,UAAkB,cAAc,MAAM,KAAK,CAAC,EAAE;AAAA,EAC9F;AAEA,SAAO;AACT;","names":["tokenizeBasic"]}