@ai-matrx/media 0.15.48 → 0.15.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/speech/configure.ts","../src/speech/unlock.ts","../src/voices/speech-text.ts","../src/voices/tts-config.ts","../src/speech/cartesia-socket.ts","../src/speech/output-sink.ts","../src/speech/sink-aware-player.ts","../src/speech/cartesia-adapter.ts","../src/speech.ts","../src/speech/speak.ts","../src/speech/playback-queue.ts","../src/speech/playback-lock.ts"],"sourcesContent":["/**\n * configureSpeech — the host's ports into THE one speech engine.\n *\n * The engine (queue, lock, Cartesia lane, sink-aware player, iOS unlock) is the same in every app;\n * what differs per host is only how it gets a provider token, which voice/speed/language the\n * person chose, how a provider refusal is reported, and which extra engines it registers. A host\n * calls `configureSpeech` once at start-up (before the first `speak`); later calls merge.\n */\n\nimport type { ProviderSessionFailure, ProviderSessionFailureVerdict } from \"@ai-matrx/agents/matrx\";\nimport type { SpeechPronunciation } from \"../voices/speech-text\";\nimport type { PlaybackAdapter, PlaybackItem, PlaybackProvider } from \"./types\";\n\n/** Voice parameters for one Cartesia utterance, resolved at START time. */\nexport interface SpeechVoiceSettings {\n /** The person's saved Cartesia voice id (null → the purpose default). */\n voice: string | null;\n language: string;\n speed: number;\n emotion?: string | null;\n}\n\nexport interface SpeechPorts {\n /** A short-lived Cartesia access token. `forceRefresh` after the provider rejected the last one. */\n getCartesiaToken: (opts?: { forceRefresh?: boolean }) => Promise<string>;\n /** Drop a token the provider rejected so the next read mints a fresh one. */\n invalidateCartesiaToken?: (token: string) => void;\n /** The person's voice / language / speed right now (tiered settings on the web). */\n resolveVoiceSettings: () => Promise<SpeechVoiceSettings>;\n /** Custom pronunciations for one utterance (best-effort; failures speak without them). */\n resolvePronunciations?: (item: PlaybackItem) => Promise<SpeechPronunciation[]>;\n /**\n * Report a browser-held provider session failure to the server; the verdict carries the\n * person's sentence and whether retrying can help.\n */\n reportProviderFailure?: (failure: ProviderSessionFailure) => Promise<ProviderSessionFailureVerdict | null>;\n /** Tell the person speech stopped (a toast on the web). Without it the error is only on the item. */\n notifyError?: (title: string, description: string) => void;\n /**\n * Runs before an item starts (e.g. \"speaking happens in an organization\" — hold until one is set).\n * Throwing marks the item failed with `describeStartRefusal(err)` (or the error's message).\n */\n beforeStart?: (item: PlaybackItem) => Promise<void>;\n describeStartRefusal?: (error: unknown) => string | null;\n /** First engagement of the engine (the web mounts its lazy audio UI here). */\n onEngage?: () => void;\n /** Extra engines (the web's server catalog lane). Cartesia is built in. */\n adapters?: Partial<Record<PlaybackProvider, () => Promise<PlaybackAdapter>>>;\n /** Engine used when a `speak` call names none. Default \"cartesia\". */\n defaultEngine?: PlaybackProvider;\n /** Fill engine-specific params a `speak` call left out (the web's catalog voice preference). */\n completeRequest?: (request: import(\"./types\").PlaybackRequest) => import(\"./types\").PlaybackRequest;\n}\n\nlet ports: Partial<SpeechPorts> = {};\n\nexport function configureSpeech(next: Partial<SpeechPorts>): void {\n ports = { ...ports, ...next, adapters: { ...ports.adapters, ...next.adapters } };\n}\n\n/** Read the configured ports (engine-internal). */\nexport function speechPorts(): Partial<SpeechPorts> {\n return ports;\n}\n\n/**\n * True when a host already wired `key` (default: the Cartesia token). A package that ships a\n * bare-host default (chat's `BareHostChrome`) fills only what nobody set, never overriding the host.\n */\nexport function isSpeechConfigured(key: keyof SpeechPorts = \"getCartesiaToken\"): boolean {\n return ports[key] !== undefined;\n}\n\n/** A port the engine cannot speak without. Throws a sentence a developer can act on. */\nexport function requirePort<K extends keyof SpeechPorts>(key: K): NonNullable<SpeechPorts[K]> {\n const value = ports[key];\n if (!value) {\n throw new Error(`Speech is not set up in this app: configureSpeech({ ${String(key)} }) was never called.`);\n }\n return value as NonNullable<SpeechPorts[K]>;\n}\n\n/** Test seam: forget every port. */\nexport function resetSpeechPortsForTests(): void {\n ports = {};\n}\n","/**\n * unlock.ts — THE iOS/WebKit audio-output unlock primitive.\n *\n * WHY THIS EXISTS (mobile-silence class, 2026-08-30)\n * --------------------------------------------------\n * On iOS every browser is WebKit (Chrome and Firefox on iPhone included), and\n * WebKit enforces two rules desktop browsers don't:\n *\n * 1. An `AudioContext` created (or resumed) OUTSIDE a user gesture starts\n * `suspended` and stays silent. Our TTS starts audio from websocket\n * callbacks seconds after the tap, so every utterance scheduled into a\n * fresh per-utterance context played into a suspended context — total\n * silence on iPhone while desktop worked fine.\n * 2. Web Audio output is muted by the ringer/silent switch unless the page\n * declares a playback audio session (`navigator.audioSession.type =\n * \"playback\"`, iOS 16.4+).\n *\n * THE FIX: capture a real user gesture ONCE, and inside it (synchronously)\n * - declare the playback audio session,\n * - create ONE shared `AudioContext`, resume it, and play a one-frame\n * silent buffer through it (the classic unlock), and\n * - prime ONE shared `HTMLAudioElement` with a muted play() so element\n * playback (the catalog engine) is also user-activated.\n *\n * Consumers then REUSE the shared unlocked context/element instead of minting\n * fresh ones outside a gesture:\n * - `SinkAwarePlayer` schedules through `getUnlockedAudioContext()` when it\n * exists (per-utterance GainNode; the context is never closed).\n * - `catalogAdapter` plays through `getPrimedMediaElement()` when it exists.\n *\n * Two entry points, both idempotent and framework-free:\n * - `installAudioUnlockListeners()` — capture-phase pointerdown/keydown\n * listeners that unlock on the user's FIRST interaction anywhere. Mounted\n * once at app root (AudioPlaybackHost), so by the time any audio plays,\n * the page is almost always already unlocked.\n * - `primeAudioOutput()` — explicit call at the top of every audio-starting\n * gesture handler (speak(), the Listen actions, transport buttons) as the\n * belt-and-suspenders for taps the global listener could miss.\n *\n * Everything feature-detects: on desktop this is a no-op-cost shared context\n * that behaves identically to before.\n *\n * THE CATEGORY IS SEQUENCED, NOT SHARED (2026-09-17)\n * -------------------------------------------------\n * `\"playback\"` is the OUTPUT category, and WebKit refuses every\n * `getUserMedia({audio})` under it (\"AudioSession category is not compatible\n * with audio capture\" — the /chat voice-input failure on iPhone). Capture needs\n * `\"play-and-record\"`. This file never touches capture: the package's mic\n * manager (`@ai-matrx/browser-audio` ≥ 0.4.0) switches the category right\n * before its own capture and restores whatever this file declared once the\n * microphone stops. Never make this file \"smarter\" about capture, and never\n * declare the category from a recording surface — one owner per direction.\n */\n\nlet sharedContext: AudioContext | null = null;\nlet sharedElement: HTMLAudioElement | null = null;\nlet listenersInstalled = false;\nlet audioSessionDeclared = false;\n\n/** One-frame silent WAV — enough to user-activate an HTMLAudioElement. */\nconst SILENT_WAV =\n \"data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=\";\n\nfunction declarePlaybackAudioSession(): void {\n if (audioSessionDeclared) return;\n // iOS 16.4+ — makes Web Audio respect \"media\" volume instead of being\n // muted by the ringer/silent switch. Absent elsewhere; feature-detected.\n const session = (\n navigator as Navigator & { audioSession?: { type: string } }\n ).audioSession;\n if (session) {\n try {\n session.type = \"playback\";\n audioSessionDeclared = true;\n } catch {\n /* older WebKit throws on unknown types — playback then follows ringer */\n }\n } else {\n audioSessionDeclared = true; // nothing to declare on this platform\n }\n}\n\n/**\n * The ONE shared output context, unlocked by a user gesture. Null until the\n * first `primeAudioOutput()` (or global-listener) gesture ran.\n */\nexport function getUnlockedAudioContext(): AudioContext | null {\n return sharedContext && sharedContext.state !== \"closed\"\n ? sharedContext\n : null;\n}\n\n/**\n * The ONE shared media element, user-activated by a muted play(). Null until\n * primed. Reused by element-based playback so `.play()` outside a gesture is\n * allowed on WebKit (activation is per-element).\n */\nexport function getPrimedMediaElement(): HTMLAudioElement | null {\n return sharedElement;\n}\n\n/**\n * Unlock audio output. MUST be called synchronously inside a user-gesture\n * call stack to have its full effect; safe (and cheap) to call any time.\n */\nexport function primeAudioOutput(): void {\n if (typeof window === \"undefined\") return;\n declarePlaybackAudioSession();\n\n // ── Web Audio ────────────────────────────────────────────────────────────\n try {\n if (!sharedContext || sharedContext.state === \"closed\") {\n sharedContext = new AudioContext();\n }\n if (sharedContext.state === \"suspended\") {\n void sharedContext.resume().catch(() => {\n /* outside a gesture this may reject — the next gesture retries */\n });\n }\n // The classic unlock: one silent frame through the graph. Repeat-safe.\n const buffer = sharedContext.createBuffer(1, 1, sharedContext.sampleRate);\n const node = sharedContext.createBufferSource();\n node.buffer = buffer;\n node.connect(sharedContext.destination);\n node.start(0);\n } catch {\n // No Web Audio (ancient browser) — element playback may still work.\n }\n\n // ── Media element ────────────────────────────────────────────────────────\n try {\n if (!sharedElement) {\n const el = new Audio();\n el.setAttribute(\"playsinline\", \"true\");\n sharedElement = el;\n }\n const el = sharedElement;\n // Only prime while idle — never disturb real playback on the element.\n if (el.paused && !el.src.startsWith(\"blob:\") && !el.currentSrc.startsWith(\"http\")) {\n el.muted = true;\n el.src = SILENT_WAV;\n void el\n .play()\n .then(() => {\n el.pause();\n el.muted = false;\n })\n .catch(() => {\n el.muted = false;\n });\n }\n } catch {\n /* priming is best-effort */\n }\n}\n\n/**\n * Install capture-phase first-interaction listeners that unlock audio on ANY\n * tap/keypress. Idempotent; the listeners stay installed (re-priming after\n * the OS suspends the page costs nothing and heals `interrupted` contexts).\n */\nexport function installAudioUnlockListeners(): void {\n if (listenersInstalled || typeof window === \"undefined\") return;\n listenersInstalled = true;\n const onGesture = () => primeAudioOutput();\n window.addEventListener(\"pointerdown\", onGesture, {\n capture: true,\n passive: true,\n });\n window.addEventListener(\"keydown\", onGesture, {\n capture: true,\n passive: true,\n });\n // iOS fires touchend-based activation more reliably than pointerdown for\n // some gestures (fast taps during scroll deceleration).\n window.addEventListener(\"touchend\", onGesture, {\n capture: true,\n passive: true,\n });\n}\n","/**\n * Speech text — THE one markdown → spoken-text conversion for read-aloud (TTS) on every client.\n * Moved from matrx-frontend (utils/markdown-processors/parse-markdown-for-speech) with the\n * extension's copy deleted (agents-chat W1, A7: TTS → media).\n */\nimport {\n findTableEnd,\n isPipeLedRow,\n replaceFences,\n rowCells,\n tableStartsAt,\n unescapeCellPipes,\n unwrapCodeSpans,\n} from \"@ai-matrx/content-ir/source\";\n\n/**\n * Every table — with or without edge pipes (THE GFM rule, gfm-table-lines) —\n * becomes one spoken sentence naming its row count and columns.\n */\nfunction speakTables(text: string): string {\n const lines = text.split(\"\\n\");\n const out: string[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n if (!tableStartsAt(lines, i)) {\n out.push(lines[i] ?? \"\");\n continue;\n }\n const end = findTableEnd(lines, i);\n const headers = rowCells(lines[i] ?? \"\").map(unescapeCellPipes).filter((h) => h.length > 0);\n const rowCount = lines.slice(i + 2, end).filter((line) => line.trim().length > 1).length;\n const headerText =\n headers.length > 1\n ? headers.slice(0, -1).join(\", \") + \", and \" + headers[headers.length - 1]\n : headers.length === 1\n ? headers[0]\n : \"unlabeled columns\";\n const rowText = rowCount === 1 ? \"one row\" : rowCount > 0 ? `${rowCount} rows` : \"no rows\";\n out.push(`There is a table with ${rowText} of data provided for ${headerText}.`);\n i = end - 1;\n }\n return out.join(\"\\n\");\n}\n\n/** Languages a listener hears by name (\"Please see the python code provided.\"). */\nconst SPOKEN_CODE_LANGUAGES: ReadonlySet<string> = new Set([\n \"javascript\", \"js\", \"typescript\", \"ts\", \"python\", \"py\", \"java\", \"csharp\", \"cs\", \"cpp\", \"c++\", \"c\",\n \"go\", \"rust\", \"php\", \"ruby\", \"swift\", \"kotlin\", \"scala\", \"sql\", \"bash\", \"shell\", \"powershell\",\n \"yaml\", \"yml\", \"json\", \"xml\", \"html\", \"css\", \"markdown\", \"md\",\n]);\n/** A term→spoken-form substitution applied before any other speech parsing. */\nexport interface SpeechPronunciation {\n from: string;\n to: string;\n}\n\nexport interface ParseMarkdownToTextOptions {\n /**\n * Custom Dictionary pronunciation substitutions. Applied FIRST — before the\n * built-in abbreviation/number normalization — so a user term like \"AI\" can\n * supply its own spoken form instead of the generic \"A I\" readout. Pairs\n * should be sorted longest-first by the caller so multi-word terms replace\n * before substrings.\n */\n pronunciations?: SpeechPronunciation[];\n}\n\n/**\n * Long-form meanings retained as reference data for search, display, or future\n * accessibility features. TTS must not use these expansions: repeatedly\n * reading \"Application Programming Interface\" for API is not natural speech.\n */\nexport const COMMON_ABBREVIATION_EXPANSIONS = {\n AI: \"Artificial Intelligence\",\n API: \"Application Programming Interface\",\n HTTP: \"Hypertext Transfer Protocol\",\n HTTPS: \"Hypertext Transfer Protocol Secure\",\n URL: \"Uniform Resource Locator\",\n URI: \"Uniform Resource Identifier\",\n JSON: \"JavaScript Object Notation\",\n XML: \"eXtensible Markup Language\",\n CSS: \"Cascading Style Sheets\",\n HTML: \"Hypertext Markup Language\",\n JS: \"JavaScript\",\n TS: \"TypeScript\",\n SQL: \"Structured Query Language\",\n DB: \"Database\",\n UI: \"User Interface\",\n UX: \"User Experience\",\n SEO: \"Search Engine Optimization\",\n SDK: \"Software Development Kit\",\n CLI: \"Command Line Interface\",\n IDE: \"Integrated Development Environment\",\n JWT: \"JSON Web Token\",\n OAuth: \"Open Authorization\",\n REST: \"Representational State Transfer\",\n CRUD: \"Create Read Update Delete\",\n MVC: \"Model View Controller\",\n SPA: \"Single Page Application\",\n SSR: \"Server Side Rendering\",\n CSR: \"Client Side Rendering\",\n PWA: \"Progressive Web App\",\n DOM: \"Document Object Model\",\n BOM: \"Browser Object Model\",\n CDN: \"Content Delivery Network\",\n CMS: \"Content Management System\",\n ERP: \"Enterprise Resource Planning\",\n CRM: \"Customer Relationship Management\",\n SaaS: \"Software as a Service\",\n PaaS: \"Platform as a Service\",\n IaaS: \"Infrastructure as a Service\",\n VPN: \"Virtual Private Network\",\n LAN: \"Local Area Network\",\n WAN: \"Wide Area Network\",\n TCP: \"Transmission Control Protocol\",\n UDP: \"User Datagram Protocol\",\n IP: \"Internet Protocol\",\n DNS: \"Domain Name System\",\n FTP: \"File Transfer Protocol\",\n SMTP: \"Simple Mail Transfer Protocol\",\n POP3: \"Post Office Protocol version 3\",\n IMAP: \"Internet Message Access Protocol\",\n} as const;\n\ntype CommonAbbreviation = keyof typeof COMMON_ABBREVIATION_EXPANSIONS;\n\n/** Escape a string for safe use inside a RegExp. */\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst COMMON_ABBREVIATION_BY_UPPERCASE = Object.fromEntries(\n (Object.keys(COMMON_ABBREVIATION_EXPANSIONS) as CommonAbbreviation[]).map(\n (abbreviation) => [abbreviation.toUpperCase(), abbreviation],\n ),\n) as Record<string, CommonAbbreviation>;\n\nconst COMMON_ABBREVIATION_PATTERN = new RegExp(\n `\\\\b(${Object.keys(COMMON_ABBREVIATION_EXPANSIONS)\n .sort((a, b) => b.length - a.length)\n .map(escapeRegExp)\n .join(\"|\")})(['’]s|s)?\\\\b`,\n \"gi\",\n);\n\n/** Acronyms normally spoken as words; leave them in conventional written form. */\nconst SPOKEN_AS_WORD = new Set<CommonAbbreviation>([\n \"JSON\",\n \"OAuth\",\n \"REST\",\n \"CRUD\",\n \"DOM\",\n \"BOM\",\n \"SaaS\",\n \"PaaS\",\n \"IaaS\",\n \"LAN\",\n \"WAN\",\n]);\n\n/**\n * These acronyms are also ordinary English words when lowercased. Only their\n * conventional acronym casing is safe to normalize.\n */\nconst CASE_SENSITIVE_WORD_ACRONYMS = new Set<CommonAbbreviation>([\n \"REST\",\n \"CRUD\",\n \"SPA\",\n \"DOM\",\n \"BOM\",\n \"LAN\",\n \"WAN\",\n]);\n\nfunction spokenFormForAbbreviation(\n match: string,\n sourceAbbreviation: string,\n suffix = \"\",\n): string {\n const abbreviation =\n COMMON_ABBREVIATION_BY_UPPERCASE[sourceAbbreviation.toUpperCase()];\n if (!abbreviation) return match;\n if (\n CASE_SENSITIVE_WORD_ACRONYMS.has(abbreviation) &&\n sourceAbbreviation !== abbreviation\n ) {\n return match;\n }\n if (SPOKEN_AS_WORD.has(abbreviation)) return `${abbreviation}${suffix}`;\n const spoken = Array.from(abbreviation.toUpperCase()).join(\" \");\n // Plural and possessive initialisms sound the same. The apostrophe gives\n // both engines an unambiguous suffix instead of turning the final \"s\" into\n // another letter in the initialism.\n return suffix ? `${spoken}'s` : spoken;\n}\n\n/**\n * Normalize technical abbreviations for both active TTS lanes.\n *\n * Cartesia Sonic 3.5 recommends single-space character delimiters for forced\n * readout (\"A P I\"), and that plain-text form also works with the catalog's\n * Groq/Orpheus engine, which has no SSML/pronunciation-dictionary input. Word\n * acronyms stay conventional so models can say \"json\", \"oh-auth\", etc.\n */\nexport function normalizeSpeechAbbreviations(text: string): string {\n if (!text) return text;\n return text.replace(COMMON_ABBREVIATION_PATTERN, spokenFormForAbbreviation);\n}\n\n/**\n * The spoken word a fill-in-the-blank run of underscores becomes. One constant\n * so every speech surface reads the same thing — change it here (e.g. \"what\")\n * and TTS + generated flashcard fronts follow.\n */\nexport const SPEECH_BLANK_WORD = \"blank\";\n\n/**\n * Fill-in-the-blank underscores → a spoken word. A run of two-or-more\n * underscores (any length — \"___\", \"_____\"), including the spaced style\n * (\"_ _ _\"), reads as \"blank\" instead of the literal \"underscore\" every TTS\n * engine would otherwise say. Generic and reusable: the centralized speech\n * cleaner AND the generated flashcard spoken-fronts both route through this, so\n * a blank sounds identical everywhere.\n *\n * Guards (so it only ever touches real blanks):\n * - bounded by non-alphanumerics → never mangles `snake_case` or `__dunder__`;\n * - a line of ONLY underscores is a markdown thematic break (divider), left\n * untouched here for the horizontal-rule rule to drop.\n */\nexport function normalizeSpeechBlanks(text: string): string {\n if (!text || !text.includes(\"_\")) return text;\n const blank = /(?<![\\p{L}\\p{N}])_(?:[ \\t]*_)+(?![\\p{L}\\p{N}])/gu;\n return text\n .split(\"\\n\")\n .map((line) =>\n /^[ \\t]*_[ \\t_]*$/.test(line)\n ? line // whole-line underscores = divider, not a blank\n : line.replace(blank, ` ${SPEECH_BLANK_WORD} `),\n )\n .join(\"\\n\");\n}\n\n/** Apply dictionary pronunciation substitutions, whole-word & case-insensitive. */\nfunction applyPronunciations(\n text: string,\n pairs: SpeechPronunciation[],\n): string {\n let out = text;\n for (const { from, to } of pairs) {\n const term = from.trim();\n if (!term || !to) continue;\n // \\b only anchors on word chars; for terms with leading/trailing non-word\n // chars fall back to a lookaround on whitespace/boundaries.\n const re = new RegExp(\n `(?<![\\\\p{L}\\\\p{N}])${escapeRegExp(term)}(?![\\\\p{L}\\\\p{N}])`,\n \"giu\",\n );\n out = out.replace(re, to);\n }\n return out;\n}\n\nexport function parseMarkdownToText(\n markdown: string,\n options?: ParseMarkdownToTextOptions,\n): string {\n if (!markdown || typeof markdown !== \"string\") return \"\";\n\n const numberToWords = (num: string): string => {\n const ones = [\n \"zero\",\n \"one\",\n \"two\",\n \"three\",\n \"four\",\n \"five\",\n \"six\",\n \"seven\",\n \"eight\",\n \"nine\",\n \"ten\",\n \"eleven\",\n \"twelve\",\n \"thirteen\",\n \"fourteen\",\n \"fifteen\",\n \"sixteen\",\n \"seventeen\",\n \"eighteen\",\n \"nineteen\",\n ];\n const tens = [\n \"\",\n \"\",\n \"twenty\",\n \"thirty\",\n \"forty\",\n \"fifty\",\n \"sixty\",\n \"seventy\",\n \"eighty\",\n \"ninety\",\n ];\n const numInt = parseInt(num, 10);\n if (numInt < 20) return ones[numInt] ?? num;\n if (numInt < 100) {\n const t = Math.floor(numInt / 10);\n const o = numInt % 10;\n return o === 0 ? (tens[t] ?? num) : `${tens[t] ?? \"\"}-${ones[o] ?? \"\"}`;\n }\n if (numInt < 1000) {\n const h = Math.floor(numInt / 100);\n const remainder = numInt % 100;\n return remainder === 0\n ? `${ones[h]} hundred`\n : `${ones[h]} hundred ${numberToWords(String(remainder))}`;\n }\n return String(numInt);\n };\n\n const emojiMap: { [key: string]: string } = {\n \"😊\": \"smiling face\",\n \"😂\": \"laughing face\",\n \"❤️\": \"heart\",\n \"👍\": \"thumbs up\",\n \"👎\": \"thumbs down\",\n \"🔥\": \"fire\",\n \"⭐\": \"star\",\n \"✅\": \"check mark\",\n \"❌\": \"cross mark\",\n \"⚠️\": \"warning\",\n \"🚀\": \"rocket\",\n \"💡\": \"light bulb\",\n \"🎯\": \"target\",\n \"📱\": \"mobile phone\",\n \"💻\": \"laptop\",\n \"🌟\": \"glowing star\",\n \"🔗\": \"link\",\n \"📧\": \"email\",\n \"📄\": \"document\",\n \"📊\": \"chart\",\n \"🛠️\": \"tools\",\n \"⚙️\": \"gear\",\n \"🔧\": \"wrench\",\n \"📈\": \"chart increasing\",\n \"📉\": \"chart decreasing\",\n \"🎉\": \"party popper\",\n \"💪\": \"flexed biceps\",\n \"🤔\": \"thinking face\",\n \"💭\": \"thought bubble\",\n \"👀\": \"eyes\",\n \"👋\": \"waving hand\",\n \"✨\": \"sparkles\",\n \"🔍\": \"magnifying mx-glass\",\n \"📝\": \"memo\",\n \"📋\": \"clipboard\",\n \"📅\": \"calendar\",\n \"⏰\": \"alarm clock\",\n \"🔒\": \"locked\",\n \"🔓\": \"unlocked\",\n \"🏆\": \"trophy\",\n \"🎖️\": \"military medal\",\n \"🎗️\": \"reminder ribbon\",\n \"🏅\": \"sports medal\",\n \"🥇\": \"first place medal\",\n \"🥈\": \"second place medal\",\n \"🥉\": \"third place medal\",\n };\n\n const measurementUnits: { [key: string]: string } = {\n // Weight\n lbs: \"pound\", // Changed from 'pounds'\n lb: \"pound\",\n oz: \"ounce\", // Changed from 'ounces'\n kg: \"kilogram\", // Changed from 'kilograms'\n gm: \"gram\", // Changed from 'grams'\n mg: \"milligram\", // Changed from 'milligrams'\n // Distance/Length\n km: \"kilometer\", // Changed from 'kilometers'\n cm: \"centimeter\", // Changed from 'centimeters'\n mm: \"millimeter\", // Changed from 'millimeters'\n ft: \"foot\", // Changed from 'feet' (Irregular plural)\n mi: \"mile\", // Changed from 'miles'\n // Speed\n mph: \"mile per hour\", // Changed from 'miles per hour'\n kph: \"kilometer per hour\", // Changed from 'kilometers per hour'\n kmh: \"kilometer per hour\", // Changed from 'kilometers per hour'\n // Volume\n ml: \"milliliter\", // Changed from 'milliliters'\n mL: \"milliliter\", // Changed from 'milliliters'\n gal: \"gallon\", // Changed from 'gallons'\n qt: \"quart\", // Changed from 'quarts'\n tbsp: \"tablespoon\", // Changed from 'tablespoons'\n tsp: \"teaspoon\", // Changed from 'teaspoons'\n // Area\n sqft: \"square foot\", // Changed from 'square feet' (Irregular plural)\n sqm: \"square meter\", // Changed from 'square meters'\n // Time\n sec: \"second\", // Changed from 'seconds'\n min: \"minute\", // Changed from 'minutes'\n hr: \"hour\", // Changed from 'hours'\n hrs: \"hour\", // Changed from 'hours'\n ms: \"millisecond\", // Changed from 'milliseconds'\n // Other common units\n psi: \"pound per square inch\", // Changed from 'pounds per square inch'\n rpm: \"revolution per minute\", // Changed from 'revolutions per minute'\n bpm: \"beat per minute\", // Changed from 'beats per minute'\n };\n\n const stripReasoningTags = (input: string): string => {\n // Remove <thinking>...</thinking> and <reasoning>...</reasoning> blocks entirely,\n // including multi-line content. Also removes any stray unclosed opening tags.\n return input\n .replace(/<(thinking|reasoning|think|reason)\\b[^>]*>[\\s\\S]*?<\\/\\1>/gi, \"\")\n .replace(/<\\/?(?:thinking|reasoning|think|reason)\\b[^>]*>/gi, \"\");\n };\n\n const stripLeadingMarkdown = (input: string): string => {\n let text = input.replace(/^\\uFEFF/, \"\").replace(/^[ \\t]*\\r?\\n/, \"\");\n // Strip a single leading markdown marker at the very start so the first\n // line reads naturally (e.g. \"## Important notice\" -> \"Important notice\").\n // Only strips ONE marker — later occurrences are handled normally below.\n text = text.replace(\n /^[ \\t]*(?:#{1,6}[ \\t]+|>[ \\t]*|[-*+][ \\t]+|\\d+\\.[ \\t]+|-[ \\t]*\\[[ xX]\\][ \\t]+)/,\n \"\",\n );\n return text;\n };\n\n // Custom Dictionary substitutions run FIRST so the spoken form is locked in\n // before built-in abbreviation/number expansion can touch the same tokens.\n // Fill-in-the-blank underscores collapse to the spoken \"blank\" up front, before\n // the emphasis (`_italic_` / `__bold__`) rules could shred a bare \"___\" run.\n const source = normalizeSpeechBlanks(\n options?.pronunciations?.length\n ? applyPronunciations(markdown, options.pronunciations)\n : markdown,\n );\n\n // Code blocks FIRST, by THE one code-range rule (@ai-matrx/content-ir/source):\n // a diagram, a named language, or plain code — each spoken as one sentence.\n // Then tables (speakTables), BEFORE any other processing that would corrupt pipe/dash syntax.\n let result = speakTables(unwrapCodeSpans(replaceFences(stripLeadingMarkdown(stripReasoningTags(source)), ({ lang }) => {\n const language = lang.toLowerCase();\n if (language === \"mermaid\") return \"Please see the diagram provided.\";\n return SPOKEN_CODE_LANGUAGES.has(language)\n ? `Please see the ${language} code provided.`\n : \"Please see the code provided.\";\n })))\n // Blank any remaining pipe-led row that was not a whole table (fallback)\n .split(\"\\n\")\n .map((line) => (isPipeLedRow(line) && line.trimEnd().endsWith(\"|\") ? \"\" : line))\n .join(\"\\n\")\n // Replace numeric ranges (e.g. 0-2, 10-20) with \"X to Y\" before any dash stripping\n .replace(\n /\\b(\\d+)\\s*[-–—]\\s*(\\d+)\\b/g,\n (_match, a, b) => `${numberToWords(a)} to ${numberToWords(b)}`,\n )\n // Headings are read as-is, ending in a period so TTS pauses after them\n .replace(/^#{1,6}\\s+(.+)$/gm, (_m, t: string) =>\n /[.!?:]$/.test(t.trim()) ? t : `${t.trim()}.`,\n )\n // Remove comment markers but keep content\n .replace(/\\/\\/\\s*/g, \"\") // Remove // comment markers\n .replace(/--\\s*/g, \"\") // Remove -- comment markers\n .replace(/#\\s+/g, \"\") // Remove # comment markers (but not headers)\n .replace(/;\\s*/g, \"\") // Remove semicolon comment markers\n // Replace bold\n .replace(/(\\*\\*|__)(.*?)\\1/g, \"$2\")\n // Replace italic\n .replace(/(\\*|_)(.*?)\\1/g, \"$2\")\n // Replace strikethrough\n .replace(/~~(.*?)~~/g, \"$1\")\n // Replace highlighting\n .replace(/==(.*?)==/g, \"$1\")\n // Replace links with descriptive text\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_match, text, url) => {\n // Check if URL is an email\n if (url.startsWith(\"mailto:\")) {\n return `${text}. Email address provided.`;\n }\n // Check for common file extensions\n const fileExtensions = [\n \".pdf\",\n \".doc\",\n \".docx\",\n \".xls\",\n \".xlsx\",\n \".ppt\",\n \".pptx\",\n \".txt\",\n \".md\",\n \".json\",\n \".xml\",\n \".csv\",\n \".zip\",\n ];\n const hasFileExtension = fileExtensions.some((ext) =>\n url.toLowerCase().includes(ext),\n );\n if (hasFileExtension) {\n return `${text}. Document link provided.`;\n }\n return `${text}. Link provided.`;\n })\n // Replace images\n .replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, ($0, alt) =>\n alt ? `${alt}. Image provided.` : \"Image provided.\",\n )\n // Replace blockquotes\n .replace(/^>\\s*(.+)$/gm, \"Quote: $1\")\n // Replace task lists\n .replace(/^-\\s*\\[x\\]\\s+(.+)$/gm, \"Completed task: $1\")\n .replace(/^-\\s*\\[\\s*\\]\\s+(.+)$/gm, \"Pending task: $1\")\n // Replace unordered lists\n .replace(/^([-*+])\\s+(.+)$/gm, \"$2\")\n // Replace ordered lists\n .replace(\n /^(\\d+)\\.\\s+(.+)$/gm,\n (_match, num, content) => `Number ${numberToWords(num)}: ${content}`,\n )\n // Replace footnotes and citations\n .replace(/\\[\\^(\\d+)\\]/g, \"Reference $1\")\n .replace(/^\\[\\d+\\]:\\s*(.+)$/gm, \"Reference: $1\")\n // Replace horizontal rules\n .replace(/^(\\*{3,}|-{3,}|_{3,})$/gm, \"\")\n // Replace mathematical expressions\n .replace(/\\$([^$]+)\\$/g, \"Mathematical expression: $1\")\n .replace(/\\$\\$([^$]+)\\$\\$/g, \"Mathematical formula: $1\")\n // Replace phone numbers\n .replace(\n /(\\+\\d{1,3}[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g,\n \"Phone number provided.\",\n )\n // Replace email addresses\n .replace(\n /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/g,\n \"Email address: $1\",\n )\n // Replace currency amounts\n .replace(/\\$(\\d+(?:,\\d{3})*(?:\\.\\d{2})?)/g, \"$1 dollars\")\n .replace(/€(\\d+(?:[.,]\\d{3})*(?:[.,]\\d{2})?)/g, \"$1 euros\")\n .replace(/£(\\d+(?:,\\d{3})*(?:\\.\\d{2})?)/g, \"$1 pounds\")\n .replace(/¥(\\d+(?:,\\d{3})*(?:\\.\\d{2})?)/g, \"$1 yen\")\n // Replace dates\n .replace(\n /(\\d{4})[-/](\\d{1,2})[-/](\\d{1,2})/g,\n (_match, year, month, day) => {\n const monthNames = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n ];\n const monthName = monthNames[parseInt(month) - 1] || month;\n return `${monthName} ${day}, ${year}`;\n },\n )\n // Replace times\n .replace(\n /(\\d{1,2}):(\\d{2})\\s*(AM|PM|am|pm)?/g,\n (_match, hour, minute, period) => {\n const hourNum = parseInt(hour);\n const periodText = period\n ? period.toUpperCase() === \"AM\"\n ? \"A.M.\"\n : \"P.M.\"\n : \"\";\n return `${hourNum} ${minute} ${periodText}`.trim();\n },\n )\n // Replace common emojis\n .replace(\n /[\\u{1F600}-\\u{1F64F}]|[\\u{1F300}-\\u{1F5FF}]|[\\u{1F680}-\\u{1F6FF}]|[\\u{1F1E0}-\\u{1F1FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu,\n (emoji) => {\n return emojiMap[emoji] || \"emoji\";\n },\n )\n // Initialisms become space-delimited letters; word acronyms stay natural.\n .replace(COMMON_ABBREVIATION_PATTERN, spokenFormForAbbreviation)\n // Replace measurement units (with word boundaries and optional numbers before)\n .replace(\n /(\\d+(?:\\.\\d+)?)\\s*(lbs|lb|oz|kg|gm|mg|km|cm|mm|ft|mi|mph|kph|kmh|ml|mL|gal|qt|tbsp|tsp|sqft|sqm|sec|min|hr|hrs|ms|psi|rpm|bpm)\\b/gi,\n (match, number, unit) => {\n const unitLower = unit.toLowerCase();\n const unitText =\n measurementUnits[unitLower] || measurementUnits[unit] || unit;\n return `${number} ${unitText}`;\n },\n )\n // Replace standalone measurement units (when preceded by space or number)\n .replace(\n /\\b(lbs|lb|oz|kg|gm|mg|km|cm|mm|mph|kph|kmh|ml|mL|gal|qt|tbsp|tsp|sqft|sqm|psi|rpm|bpm)(?=\\s|$|[.,;!?])/gi,\n (match) => {\n const unitLower = match.toLowerCase();\n return measurementUnits[unitLower] || measurementUnits[match] || match;\n },\n )\n // Replace special characters and symbols\n .replace(/©/g, \"copyright\")\n .replace(/®/g, \"registered trademark\")\n .replace(/™/g, \"trademark\")\n .replace(/°/g, \"degrees\")\n .replace(/±/g, \"plus or minus\")\n .replace(/≈/g, \"approximately\")\n .replace(/≠/g, \"not equal to\")\n .replace(/≤/g, \"less than or equal to\")\n .replace(/≥/g, \"greater than or equal to\")\n .replace(/=/g, \" equals \")\n .replace(/→/g, \"arrow\")\n .replace(/←/g, \"left arrow\")\n .replace(/↑/g, \"up arrow\")\n .replace(/↓/g, \"down arrow\")\n // Remove HTML tags, keep content\n .replace(/<\\/?[^>]+(>|$)/g, \"\")\n // Add period before newlines that follow text without terminal punctuation (ensures TTS pause)\n .replace(/([^\\s.!?,;:\\-])(\\n)/g, \"$1.\\n\")\n // Clean up multiple spaces\n .replace(/\\s+/g, \" \")\n // Trim whitespace\n .trim();\n\n return result;\n}\n","/**\n * Central Cartesia TTS configuration — the single source of truth for the model,\n * API version, system default voices, speed/volume baselines, and playback\n * buffering used by every in-app TTS surface (chat read-aloud, studio\n * read-aloud, voice playgrounds, the admin tester).\n *\n * Moved from matrx-frontend lib/cartesia/config.ts (P16 voices). The SDK-typed\n * `buildGenerationConfig` stays with the host's Cartesia client.\n */\n\n\n/** Current model + API version for all in-app TTS. */\nexport const TTS_MODEL_ID = \"sonic-3.5\";\nexport const CARTESIA_API_VERSION = \"2026-08-14\";\n\n/**\n * System default voices, used only when a user has not chosen their own.\n * - reading → Skylar (primary female; document / read-aloud)\n * - assistant → Daniel (primary male; assistant replies)\n */\nexport const READING_VOICE_ID = \"db6b0ed5-d5d3-463d-ae85-518a07d3c2b4\";\nexport const ASSISTANT_VOICE_ID = \"47c38ca4-5f35-497b-b1a3-415245fb35e1\";\n\n/**\n * The pre-2026 hardcoded default voice. Treated as \"unset\" by resolveVoiceId so\n * users who never explicitly chose a voice transition to the new defaults.\n */\nexport const LEGACY_DEFAULT_VOICE_ID = \"156fb8d2-335b-4950-9cb3-a2d33befec77\";\n\nexport type VoicePurpose = \"reading\" | \"assistant\";\n\n/** generation_config.speed range (1.0 = original). Our chosen baseline is 1.2. */\nexport const TTS_SPEED_MIN = 0.6;\nexport const TTS_SPEED_MAX = 1.5;\nexport const TTS_DEFAULT_SPEED = 1.2;\n/** generation_config.volume range (1.0 = original). */\nexport const TTS_DEFAULT_VOLUME = 1.0;\n\n/**\n * Client-side player buffer (seconds). Higher than the old 0.25s, which\n * caused stream underruns heard as choppy \"pauses\"; tune in one place.\n */\nexport const TTS_PLAYBACK_BUFFER_SEC = 0.7;\n\n/**\n * Lower buffer for token-by-token streaming (real-time LLM speech) where\n * latency matters more — still well above the old 0.1s that stuttered.\n */\nexport const TTS_STREAMING_BUFFER_SEC = 0.3;\n\n/** A user's explicit voice preference wins; otherwise the purpose default. */\nexport function resolveVoiceId(\n userVoiceId: string | null | undefined,\n purpose: VoicePurpose,\n): string {\n // MATRX-EXCEPTION: honest default for an explicitly-optional param — falls\n // through to the purpose default below, not a boundary write.\n const v = (userVoiceId ?? \"\").trim();\n if (v && v !== LEGACY_DEFAULT_VOICE_ID) return v;\n return purpose === \"reading\" ? READING_VOICE_ID : ASSISTANT_VOICE_ID;\n}\n\n/** Clamp a stored speed to the valid generation_config range, else the default. */\nexport function resolveSpeed(userSpeed: number | null | undefined): number {\n if (\n typeof userSpeed === \"number\" &&\n userSpeed >= TTS_SPEED_MIN &&\n userSpeed <= TTS_SPEED_MAX\n ) {\n return userSpeed;\n }\n return TTS_DEFAULT_SPEED;\n}\n","/**\n * THE single way to open a Cartesia TTS websocket from a browser (moved from matrx-frontend\n * lib/cartesia/connection.ts; token, failure report and toast arrive through configureSpeech).\n *\n * Cartesia v4 exposes a snake_case, event based websocket. The rest of the\n * app intentionally keeps one small, stable streaming contract, so SDK\n * upgrades stay contained here instead of leaking through every TTS hook.\n */\n\n// Types only at import: the Cartesia SDK (~20 kB gzip) loads when a socket is first opened, so every\n// importer of the speech entry (chat's composer via ./react) pays nothing until read-aloud runs.\nimport type { Cartesia } from \"@cartesia/cartesia-js/client\";\nimport type { GenerationConfig, VoiceSpecifier } from \"@cartesia/cartesia-js/resources/tts\";\nimport type { SupportedLanguage } from \"@cartesia/cartesia-js/resources/voices\";\nimport type { ProviderSessionFailure, ProviderSessionFailureVerdict } from \"@ai-matrx/agents/matrx\";\nimport { CARTESIA_API_VERSION } from \"../voices/tts-config\";\nimport { requirePort, speechPorts } from \"./configure\";\n\n/**\n * A provider session failure carrying the server's verdict: `message` is the sentence to show\n * (the provider's raw text when the report failed), and `retryable` says whether reconnecting helps.\n */\nexport class ProviderSessionError extends Error {\n override readonly name = \"ProviderSessionError\";\n readonly provider: ProviderSessionFailure[\"provider\"];\n readonly retryable: boolean;\n readonly errorType: string | null;\n readonly reported: boolean;\n\n constructor(\n failure: ProviderSessionFailure,\n verdict: ProviderSessionFailureVerdict | null,\n fallbackMessage: string,\n options?: { cause?: unknown },\n ) {\n super(verdict?.user_message || fallbackMessage, options);\n this.provider = failure.provider;\n // An unreported failure keeps the caller's existing retry behavior.\n this.retryable = verdict ? verdict.retryable : true;\n this.errorType = verdict?.error_type ?? failure.error_type ?? null;\n this.reported = verdict !== null;\n }\n}\n\n/** True when the provider rejected the token (refresh once and retry). */\nexport function isCartesiaAuthError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error ?? \"\");\n return /\\b(401|403)\\b|unauthorized|forbidden|invalid.*token|expired.*token|authentication/i.test(message);\n}\n\nasync function reportFailure(failure: ProviderSessionFailure): Promise<ProviderSessionFailureVerdict | null> {\n const report = speechPorts().reportProviderFailure;\n if (!report) return null;\n try {\n return await report(failure);\n } catch {\n return null;\n }\n}\n\nexport interface CartesiaTtsSocketOptions {\n container?: string;\n encoding?: string;\n sampleRate?: number;\n}\n\nexport type CartesiaTtsVoice = { mode: \"id\"; id: string };\n\nexport interface CartesiaTtsRequest {\n modelId: string;\n transcript: string;\n voice: CartesiaTtsVoice;\n language?: SupportedLanguage;\n contextId?: string;\n continue?: boolean;\n addTimestamps?: boolean;\n addPhonemeTimestamps?: boolean;\n maxBufferDelayMs?: number;\n generationConfig?: GenerationConfig;\n}\n\ntype NativeSocket = Awaited<ReturnType<Cartesia[\"tts\"][\"websocket\"]>>;\ntype CartesiaErrorEvent = {\n type: \"error\";\n message?: string;\n title?: string;\n status_code?: number;\n error_code?: string;\n context_id?: string;\n};\ntype MessageListener = (message: string) => void;\n\n/** The structural source consumed by SinkAwarePlayer. */\nexport class CartesiaAudioSource {\n readonly sampleRate: number;\n #chunks: Float32Array[] = [];\n #done = false;\n #waiter: (() => void) | null = null;\n\n constructor(sampleRate: number) {\n this.sampleRate = sampleRate;\n }\n\n durationToSampleCount(durationSecs: number): number {\n return Math.ceil(durationSecs * this.sampleRate);\n }\n\n push(bytes: Uint8Array): void {\n const sampleCount = Math.floor(bytes.byteLength / Float32Array.BYTES_PER_ELEMENT);\n const samples = new Float32Array(sampleCount);\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n for (let index = 0; index < sampleCount; index += 1) {\n samples[index] = view.getFloat32(index * Float32Array.BYTES_PER_ELEMENT, true);\n }\n this.#chunks.push(samples);\n this.#wake();\n }\n\n finish(): void {\n this.#done = true;\n this.#wake();\n }\n\n async read(destination: Float32Array): Promise<number> {\n let written = 0;\n while (written < destination.length) {\n while (this.#chunks.length === 0 && !this.#done) {\n await new Promise<void>((resolve) => {\n this.#waiter = resolve;\n });\n }\n if (this.#chunks.length === 0) break;\n const chunk = this.#chunks[0]!;\n const count = Math.min(chunk.length, destination.length - written);\n destination.set(chunk.subarray(0, count), written);\n written += count;\n if (count === chunk.length) {\n this.#chunks.shift();\n } else {\n this.#chunks[0] = chunk.subarray(count);\n }\n }\n return written;\n }\n\n #wake(): void {\n this.#waiter?.();\n this.#waiter = null;\n }\n}\n\nexport interface CartesiaTtsResponse {\n source: CartesiaAudioSource;\n on(event: \"message\", listener: MessageListener): void;\n}\n\nexport interface CartesiaConnectionCtx {\n on(event: \"close\", listener: () => void): void;\n}\n\nexport interface CartesiaTtsSocket {\n send(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse>;\n continue(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse>;\n disconnect(): void;\n}\n\nconst DEFAULTS: Required<CartesiaTtsSocketOptions> = {\n container: \"raw\",\n encoding: \"pcm_f32le\",\n sampleRate: 44100,\n};\n\ntype ResolvedSocketOptions = {\n container: \"raw\";\n encoding: \"pcm_f32le\";\n sampleRate: 8000 | 16000 | 22050 | 24000 | 44100 | 48000;\n};\n\nclass CartesiaV4Socket implements CartesiaTtsSocket {\n #native: NativeSocket;\n #options: ResolvedSocketOptions;\n #sources = new Map<string, CartesiaAudioSource>();\n #listeners = new Map<string, Set<MessageListener>>();\n\n /** Set when the provider refused this account and reconnecting cannot help. */\n #refused: ProviderSessionError | null = null;\n\n constructor(native: NativeSocket, options: ResolvedSocketOptions) {\n this.#native = native;\n this.#options = options;\n // The SDK also emits its typed `error` channel after the global event.\n // Bind it so a provider error does not become an unhandled rejection;\n // the event handler below finishes the affected source and notifies callers.\n native.on(\"error\", () => {});\n native.on(\"event\", (event) => {\n const contextId = \"context_id\" in event ? event.context_id : undefined;\n if (event.type === \"error\") {\n // A provider error is terminal for its context (or for every context\n // when it names none): end the audio now, and hand the error to its\n // listeners once the server has classified it.\n const listeners = contextId ? this.#listeners.get(contextId) : undefined;\n if (contextId) {\n this.#sources.get(contextId)?.finish();\n this.#sources.delete(contextId);\n this.#listeners.delete(contextId);\n } else {\n for (const source of this.#sources.values()) source.finish();\n this.#sources.clear();\n this.#listeners.clear();\n }\n void this.#reportProviderError(event, listeners);\n return;\n }\n if (!contextId) return;\n const source = this.#sources.get(contextId);\n if (event.type === \"chunk\" && source) {\n source.push(event.audio ?? decodeBase64(event.data));\n } else if (event.type === \"done\") {\n source?.finish();\n }\n const serialized = JSON.stringify(event);\n for (const listener of this.#listeners.get(contextId) ?? []) {\n listener(serialized);\n }\n if (event.type === \"done\") {\n this.#sources.delete(contextId);\n this.#listeners.delete(contextId);\n }\n });\n }\n\n /**\n * Browser-held session: the server never sees this provider error unless we\n * report it. Listeners get the event with `message` replaced by the server's\n * sentence, and the person sees it as a toast.\n */\n async #reportProviderError(\n event: CartesiaErrorEvent,\n listeners: Set<MessageListener> | undefined,\n ): Promise<void> {\n const failure: ProviderSessionFailure = {\n provider: \"cartesia\",\n status_code: event.status_code ?? null,\n error_type: event.error_code ?? event.title ?? null,\n message: event.message || event.title || \"Cartesia TTS error\",\n };\n const verdict = await reportFailure(failure);\n const error = new ProviderSessionError(failure, verdict, failure.message);\n if (!error.retryable) this.#refused = error;\n const delivered = JSON.stringify({\n ...event,\n message: error.message,\n retryable: error.retryable,\n });\n for (const listener of listeners ?? []) listener(delivered);\n // Most consumers only play the audio source, which just ends — without\n // this the refusal would be silent.\n speechPorts().notifyError?.(\"Speech stopped\", error.message);\n }\n\n async send(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse> {\n // A refusal reconnecting cannot fix (e.g. out of credit) stops this socket:\n // the SDK would otherwise reconnect transparently and ask again.\n if (this.#refused) throw this.#refused;\n const contextId = request.contextId ?? crypto.randomUUID();\n let source = this.#sources.get(contextId);\n if (!source) {\n source = new CartesiaAudioSource(this.#options.sampleRate);\n this.#sources.set(contextId, source);\n }\n\n await this.#native.send(({\n context_id: contextId,\n model_id: request.modelId,\n transcript: request.transcript,\n voice: toNativeVoice(request.voice),\n output_format: {\n container: this.#options.container,\n encoding: this.#options.encoding,\n sample_rate: this.#options.sampleRate,\n },\n language: request.language,\n continue: request.continue,\n add_timestamps: request.addTimestamps,\n add_phoneme_timestamps: request.addPhonemeTimestamps,\n max_buffer_delay_ms: request.maxBufferDelayMs,\n generation_config: request.generationConfig,\n }) as Parameters<NativeSocket[\"send\"]>[0]);\n\n return {\n source,\n on: (_event, listener) => {\n const listeners = this.#listeners.get(contextId) ?? new Set<MessageListener>();\n listeners.add(listener);\n this.#listeners.set(contextId, listeners);\n },\n };\n }\n\n continue(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse> {\n return this.send(request);\n }\n\n disconnect(): void {\n for (const source of this.#sources.values()) source.finish();\n this.#sources.clear();\n this.#listeners.clear();\n this.#native.close();\n }\n}\n\nfunction decodeBase64(value: string): Uint8Array {\n const decoded = atob(value);\n const bytes = new Uint8Array(decoded.length);\n for (let index = 0; index < decoded.length; index += 1) {\n bytes[index] = decoded.charCodeAt(index);\n }\n return bytes;\n}\n\nfunction toNativeVoice(voice: CartesiaTtsVoice): VoiceSpecifier {\n return { id: voice.id };\n}\n\nasync function buildConnectedSocket(\n token: string,\n options: ResolvedSocketOptions,\n): Promise<{ ws: CartesiaV4Socket; ctx: CartesiaConnectionCtx }> {\n const { Cartesia: CartesiaClient } = await import(\"@cartesia/cartesia-js/client\");\n const client = new CartesiaClient({ token });\n const native = await client.tts.websocket({\n headers: { \"Cartesia-Version\": CARTESIA_API_VERSION },\n });\n await native.connect();\n const closeListeners = new Set<() => void>();\n native.socket.addEventListener(\"close\", () => {\n for (const listener of closeListeners) listener();\n });\n return {\n ws: new CartesiaV4Socket(native, options),\n ctx: {\n on: (_event, listener) => {\n closeListeners.add(listener);\n },\n },\n };\n}\n\n/** Open a connected socket, refreshing a rejected broker token once. */\nexport async function connectCartesiaTts(\n options?: CartesiaTtsSocketOptions,\n): Promise<{ ws: CartesiaTtsSocket; ctx: CartesiaConnectionCtx }> {\n const requested = { ...DEFAULTS, ...options };\n if (requested.container !== \"raw\") {\n throw new Error(\"Cartesia WebSocket playback requires the raw audio container.\");\n }\n if (requested.encoding !== \"pcm_f32le\") {\n throw new Error(\"Cartesia browser playback requires pcm_f32le audio.\");\n }\n const sampleRates: readonly number[] = [8000, 16000, 22050, 24000, 44100, 48000];\n if (!sampleRates.includes(requested.sampleRate)) {\n throw new Error(`Unsupported Cartesia sample rate: ${requested.sampleRate}`);\n }\n const resolved = requested as ResolvedSocketOptions;\n const getToken = requirePort(\"getCartesiaToken\");\n const token = await getToken();\n try {\n try {\n return await buildConnectedSocket(token, resolved);\n } catch (error) {\n if (!isCartesiaAuthError(error)) throw error;\n speechPorts().invalidateCartesiaToken?.(token);\n const refreshed = await getToken({ forceRefresh: true }).catch(\n (mintError: unknown) => {\n throw new BrokerMintFailure(mintError);\n },\n );\n return await buildConnectedSocket(refreshed, resolved);\n }\n } catch (error) {\n // The broker mint already reports its own failures server-side; only the\n // browser → Cartesia handshake is reported here.\n if (error instanceof BrokerMintFailure) throw error.cause;\n throw await reportCartesiaConnectFailure(error);\n }\n}\n\n/** Marks a mint failure inside the retry so it is not reported as Cartesia's. */\nclass BrokerMintFailure extends Error {\n constructor(override readonly cause: unknown) {\n super(\"broker mint failed\");\n }\n}\n\nasync function reportCartesiaConnectFailure(\n error: unknown,\n): Promise<ProviderSessionError> {\n const raw =\n error instanceof Error\n ? error.message\n : typeof error === \"object\" && error && \"message\" in error\n ? String((error as { message: unknown }).message)\n : \"\";\n const status = /\\b([45]\\d\\d)\\b/.exec(raw)?.[1];\n const failure: ProviderSessionFailure = {\n provider: \"cartesia\",\n status_code: status ? Number(status) : null,\n message: raw || \"Cartesia WebSocket connection failed\",\n };\n const verdict = await reportFailure(failure);\n return new ProviderSessionError(\n failure,\n verdict,\n raw || \"Could not connect to the speech service.\",\n { cause: error },\n );\n}\n","// features/audio/audioOutputSink.ts\n//\n// Universal audio OUTPUT (speaker) routing — the single place the app decides\n// which physical output device every sound plays through.\n//\n// WHY THIS EXISTS\n// ---------------\n// Two unrelated playback paths exist: real `HTMLMediaElement`s (`<audio>` /\n// `<video>` via `InlineMediaRef`) and Web Audio playback via our\n// `SinkAwarePlayer` (features/audio/sinkAwarePlayer.ts — the fork of the\n// Cartesia SDK WebPlayer). The browser exposes TWO sink APIs for these:\n//\n// • `HTMLMediaElement.setSinkId(deviceId)` — Chrome/Firefox; routes one media\n// element. Used directly by `InlineMediaRef` via `applySinkToMediaElement`.\n// • `AudioContext.setSinkId(deviceId)` — Chromium-only; routes a Web Audio\n// graph. `SinkAwarePlayer` owns its per-utterance contexts and calls this\n// itself: it reads this store at context creation and subscribes for\n// mid-utterance device changes. (The old global `AudioContext` constructor\n// monkeypatch — `installAudioContextSinkRouting` + the `NO_SINK_ROUTING`\n// opt-out sentinel — is DELETED; nothing patches globals anymore.)\n//\n// SAFARI has NEITHER API. Everything here feature-detects and no-ops gracefully;\n// on Safari the user picks the output device in macOS/iOS settings.\n//\n// One store, one subscribe channel, two appliers. The audio-devices manager is\n// the only writer (`setPreferredOutputDeviceId`); InlineMediaRef +\n// SinkAwarePlayer are the readers.\n\ntype SinkListener = (deviceId: string) => void;\n\ninterface SinkState {\n /** Current preferred output deviceId. \"\" = system default. */\n deviceId: string;\n listeners: Set<SinkListener>;\n}\n\nconst state: SinkState = {\n deviceId: \"\",\n listeners: new Set(),\n};\n\n/** True when `HTMLMediaElement.setSinkId` exists (Chrome/Firefox, not Safari). */\nexport function mediaElementSinkSupported(): boolean {\n return (\n typeof HTMLMediaElement !== \"undefined\" &&\n \"setSinkId\" in HTMLMediaElement.prototype\n );\n}\n\n/** True when `AudioContext.setSinkId` exists (Chromium only). */\nexport function audioContextSinkSupported(): boolean {\n if (typeof window === \"undefined\") return false;\n const Ctor =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext })\n .webkitAudioContext;\n return !!Ctor && \"setSinkId\" in Ctor.prototype;\n}\n\n/**\n * True when the browser can route output at all (either API). When false, the\n * speaker picker is disabled and the user is told to choose output in OS\n * settings.\n */\nexport function outputSelectionSupported(): boolean {\n return mediaElementSinkSupported() || audioContextSinkSupported();\n}\n\n/** Read the current preferred output deviceId (\"\" = system default). */\nexport function getPreferredOutputDeviceId(): string {\n return state.deviceId;\n}\n\n/**\n * Set the preferred output device. Notifies subscribers (InlineMediaRef\n * re-applies to its live media elements; SinkAwarePlayer re-routes any live\n * playback context mid-utterance). Idempotent — a no-op when unchanged.\n */\nexport function setPreferredOutputDeviceId(deviceId: string): void {\n if (state.deviceId === deviceId) return;\n state.deviceId = deviceId;\n for (const l of state.listeners) {\n try {\n l(deviceId);\n } catch {\n // never let a listener break the store\n }\n }\n}\n\n/** Subscribe to preferred-output changes. Returns an unsubscribe fn. */\nexport function subscribeOutputDevice(listener: SinkListener): () => void {\n state.listeners.add(listener);\n return () => {\n state.listeners.delete(listener);\n };\n}\n\n/**\n * Apply the current preferred sink to one `HTMLMediaElement`. Feature-detected;\n * on Safari (no `setSinkId`) or \"\" (system default) it's a clean no-op. A real\n * failure (e.g. the device vanished) is reported loudly, never swallowed — the\n * element keeps playing on the default device.\n */\nexport async function applySinkToMediaElement(\n el: HTMLMediaElement | null | undefined,\n): Promise<void> {\n if (!el) return;\n if (!mediaElementSinkSupported()) return;\n const target = state.deviceId;\n // Empty string is a valid argument to setSinkId meaning \"system default\".\n const elWithSink = el as HTMLMediaElement & {\n sinkId?: string;\n setSinkId?: (id: string) => Promise<void>;\n };\n if (elWithSink.sinkId === target) return; // already routed\n try {\n await elWithSink.setSinkId?.(target);\n } catch (err) {\n\n console.error(\n \"[audioOutputSink] setSinkId on media element failed — falling back to \" +\n \"the system default device. Requested device may be unavailable.\",\n { deviceId: target, error: err },\n );\n }\n}\n","// features/audio/sinkAwarePlayer.ts\n//\n// SinkAwarePlayer — our fork of the Cartesia SDK `WebPlayer` (~140 lines\n// upstream), kept drop-in compatible with its playback contract\n// (`play(source)` / `pause()` / `resume()` / `toggle()` / `stop()`), but\n// OWNING its AudioContext so output-device (speaker) routing is explicit\n// instead of relying on a global `AudioContext` constructor monkeypatch.\n//\n// WHY THE FORK EXISTS (FOUND_DEFECTS D13)\n// ---------------------------------------\n// The SDK's WebPlayer builds a hard-private AudioContext per `play()` call\n// with no accessible handle, so the only way to route it to the user's chosen\n// speaker was patching the global `AudioContext` constructor\n// (`installAudioContextSinkRouting`, now deleted). That patch was invisible\n// action-at-a-distance: every context in the app inherited the sink unless it\n// opted out via a sentinel, and a device change could never reach a live\n// utterance. This fork:\n//\n// 1. Applies the preferred output device (from the same store the\n// `userPreferences.mediaDevices` module feeds via\n// `providers/AudioDeviceProviderImpl` → `applyOutputDevice`) to its own\n// context at creation, and\n// 2. Subscribes to `subscribeOutputDevice` while a context is live, so a\n// device change re-routes MID-UTTERANCE (`AudioContext.setSinkId`).\n//\n// BROWSER SUPPORT: `AudioContext.setSinkId` is Chromium-only. On Firefox and\n// Safari the sink calls feature-detect and no-op — playback continues on the\n// SYSTEM DEFAULT device (the user picks output in OS settings), never a crash.\n// A real setSinkId failure (device unplugged mid-play) is reported loudly and\n// playback continues on the default device.\n//\n// CONTRACT PARITY WITH THE SDK WebPlayer\n// --------------------------------------\n// - `play(source)` builds a fresh AudioContext per utterance at the source's\n// sample rate, reads `bufferDuration`-sized chunks, schedules them\n// back-to-back, and resolves when playback finishes.\n// - `pause()` / `resume()` / `toggle()` suspend/resume the context and throw\n// \"AudioContext not initialized.\" before the first play — callers guard\n// with hasPlayed refs / try-catch exactly as they did against the SDK.\n// - `stop()` closes the context. Unlike the SDK it is close-idempotent: a\n// second stop on an already-closed context is a silent no-op (the SDK threw\n// \"Cannot close a closed AudioContext\", which every caller had to catch).\n\nimport {\n getPreferredOutputDeviceId,\n subscribeOutputDevice,\n} from \"./output-sink\";\nimport { getUnlockedAudioContext } from \"./unlock\";\n\n/**\n * The slice of the Cartesia `Source` contract the player actually consumes.\n * Structural — `@cartesia/cartesia-js`'s `Source` satisfies it — so the\n * player has no hard SDK type dependency and is unit-testable with a stub.\n */\nexport interface PlayableSource {\n readonly sampleRate: number;\n /** Read up to `dst.length` samples; resolves with the count actually read.\n * A short read (< dst.length) signals the end of the source. */\n read(dst: Float32Array): Promise<number>;\n /** Number of samples covering `durationSecs` at this source's sample rate. */\n durationToSampleCount(durationSecs: number): number;\n}\n\n/** Minimal AudioContext surface the player uses — injectable for tests. */\nexport interface SinkAwareAudioContext {\n readonly currentTime: number;\n readonly state: AudioContextState;\n readonly destination: AudioNode;\n createBufferSource(): AudioBufferSourceNode;\n createBuffer(\n channels: number,\n length: number,\n sampleRate: number,\n ): AudioBuffer;\n suspend(): Promise<void>;\n resume(): Promise<void>;\n close(): Promise<void>;\n /** Chromium-only; absent on Firefox/Safari. */\n setSinkId?: (deviceId: string) => Promise<void>;\n}\n\nexport interface SinkAwarePlayerOptions {\n /** Seconds of audio to buffer per scheduling chunk (same as the SDK). */\n bufferDuration: number;\n /**\n * Test seam: build the per-utterance context. Defaults to\n * `new AudioContext({ sampleRate })`.\n */\n createContext?: (sampleRate: number) => SinkAwareAudioContext;\n}\n\nfunction defaultCreateContext(sampleRate: number): SinkAwareAudioContext {\n return new AudioContext({ sampleRate });\n}\n\nexport class SinkAwarePlayer {\n #context: SinkAwareAudioContext | null = null;\n #startNextPlaybackAt = 0;\n #bufferDuration: number;\n #createContext: (sampleRate: number) => SinkAwareAudioContext;\n #unsubscribeSink: (() => void) | null = null;\n /**\n * True when `#context` is the app's SHARED gesture-unlocked context\n * (features/audio/unlock.ts) rather than a private per-utterance one.\n * Shared mode exists for iOS/WebKit, where a context created outside a\n * user gesture starts `suspended` forever — our audio starts from\n * websocket callbacks, so per-utterance contexts were silent on iPhone.\n * In shared mode the utterance plays through a per-utterance GainNode and\n * `stop()` tears that down instead of closing the context.\n */\n #sharedMode = false;\n #utteranceGain: GainNode | null = null;\n #liveSources = new Set<AudioBufferSourceNode>();\n #stopped = false;\n\n constructor({ bufferDuration, createContext }: SinkAwarePlayerOptions) {\n this.#bufferDuration = bufferDuration;\n this.#createContext = createContext ?? defaultCreateContext;\n }\n\n /**\n * Play audio from a source. Resolves when the audio has finished playing.\n *\n * Prefers the app's shared gesture-unlocked context (required for sound on\n * iOS/WebKit); otherwise builds a fresh AudioContext per call (matching the\n * SDK). Either way the output is routed to the user's preferred device and\n * re-routed live on device change. Web Audio resamples per-buffer, so the\n * shared context's hardware rate plays 44.1k sources correctly.\n */\n async play(source: PlayableSource): Promise<void> {\n this.#startNextPlaybackAt = 0;\n this.#stopped = false;\n this.#detachSinkSubscription();\n // Custom createContext (tests) keeps full legacy behavior.\n const shared = this.#createContext === defaultCreateContext\n ? getUnlockedAudioContext()\n : null;\n const context: SinkAwareAudioContext =\n shared ?? this.#createContext(source.sampleRate);\n this.#sharedMode = shared !== null;\n this.#context = context;\n if (this.#sharedMode) {\n const ctx = shared as AudioContext;\n this.#utteranceGain = ctx.createGain();\n this.#utteranceGain.connect(ctx.destination);\n } else {\n this.#utteranceGain = null;\n }\n this.#attachSinkRouting(context);\n\n // A non-running context plays NOTHING while accepting every schedule\n // call — the exact silent-on-iPhone failure. iOS also parks contexts in a\n // non-standard \"interrupted\" state after a phone call / Siri. Resume\n // (allowed once the page was gesture-unlocked); if it won't run, fail\n // LOUDLY so the queue and the Listen panel show an error with a\n // tap-to-retry instead of silence.\n if ((context.state as string) !== \"running\") {\n try {\n await context.resume();\n } catch {\n /* judged below by the resulting state */\n }\n if ((context.state as string) !== \"running\") {\n throw new Error(\n \"The browser blocked audio output — tap the play button to start sound.\",\n );\n }\n }\n\n try {\n const buffer = new Float32Array(\n source.durationToSampleCount(this.#bufferDuration),\n );\n const plays: Promise<void>[] = [];\n while (true) {\n const read = await source.read(buffer);\n // A short read means the source is exhausted — only play what was\n // actually filled, otherwise the tail of the previous chunk repeats.\n const playableAudio = buffer.subarray(0, read);\n plays.push(this.#playBuffer(playableAudio, source.sampleRate));\n if (read < buffer.length) break;\n }\n await Promise.all(plays);\n } finally {\n // The utterance is over (or failed) — stop tracking device changes for\n // this context. The context itself stays open until stop(), matching\n // the SDK (pause/resume after natural completion remain valid).\n this.#detachSinkSubscription();\n }\n }\n\n /** Suspend playback. Throws before the first play (contract parity). */\n async pause(): Promise<void> {\n const context = this.#requireContext();\n if (this.#sharedMode && this.#stopped) return;\n await context.suspend();\n }\n\n /** Resume suspended playback. Throws before the first play. */\n async resume(): Promise<void> {\n const context = this.#requireContext();\n if (this.#sharedMode && this.#stopped) return;\n await context.resume();\n }\n\n /** Pause when running, resume otherwise. Throws before the first play. */\n async toggle(): Promise<void> {\n if (this.#requireContext().state === \"running\") {\n await this.pause();\n } else {\n await this.resume();\n }\n }\n\n /**\n * Stop playback. Throws before the first play (contract parity); a repeat\n * stop is a silent no-op.\n *\n * Owned-context mode closes the context (the SDK contract). Shared mode\n * NEVER closes the app-wide unlocked context — it stops this utterance's\n * scheduled sources and tears down its GainNode, and resumes the context\n * if this utterance had paused it (a suspended shared context would mute\n * the NEXT utterance too).\n */\n async stop(): Promise<void> {\n const context = this.#requireContext();\n this.#detachSinkSubscription();\n if (this.#sharedMode) {\n if (this.#stopped) return;\n this.#stopped = true;\n for (const node of this.#liveSources) {\n try {\n node.stop();\n } catch {\n /* already ended */\n }\n }\n this.#liveSources.clear();\n try {\n this.#utteranceGain?.disconnect();\n } catch {\n /* already disconnected */\n }\n this.#utteranceGain = null;\n if (context.state === \"suspended\") {\n await context.resume().catch(() => {});\n }\n return;\n }\n if (context.state === \"closed\") return;\n await context.close();\n }\n\n #requireContext(): SinkAwareAudioContext {\n if (!this.#context) {\n throw new Error(\"AudioContext not initialized.\");\n }\n return this.#context;\n }\n\n /**\n * Route `context` to the current preferred output device and keep it\n * routed while it lives. Feature-detected: on Firefox/Safari (no\n * `AudioContext.setSinkId`) this is a clean no-op and audio plays on the\n * system default device.\n */\n #attachSinkRouting(context: SinkAwareAudioContext): void {\n if (typeof context.setSinkId !== \"function\") return;\n\n const applySink = (deviceId: string) => {\n if (this.#context !== context || context.state === \"closed\") return;\n // \"\" = system default. Unlike HTMLMediaElement.setSinkId, passing \"\"\n // to AudioContext.setSinkId is valid and means the default device.\n context.setSinkId?.(deviceId).catch((err: unknown) => {\n console.error(\n \"[SinkAwarePlayer] setSinkId failed — playback continues on the \" +\n \"system default device. The requested device may be unavailable.\",\n { deviceId, error: err },\n );\n });\n };\n\n // Apply the persisted choice now (skip \"\" — a fresh context already IS\n // the system default)…\n const initial = getPreferredOutputDeviceId();\n if (initial) applySink(initial);\n // …and follow changes mid-utterance.\n this.#unsubscribeSink = subscribeOutputDevice(applySink);\n }\n\n #detachSinkSubscription(): void {\n this.#unsubscribeSink?.();\n this.#unsubscribeSink = null;\n }\n\n async #playBuffer(buf: Float32Array, sampleRate: number): Promise<void> {\n const context = this.#requireContext();\n if (buf.length === 0) return;\n if (this.#sharedMode && this.#stopped) return;\n const startAt = this.#startNextPlaybackAt;\n const duration = buf.length / sampleRate;\n this.#startNextPlaybackAt =\n duration + Math.max(context.currentTime, this.#startNextPlaybackAt);\n\n const sourceNode = context.createBufferSource();\n // The buffer carries the SOURCE sample rate; when it differs from the\n // context's hardware rate (shared mode), Web Audio resamples on playback.\n const audioBuffer = context.createBuffer(1, buf.length, sampleRate);\n audioBuffer.getChannelData(0).set(buf);\n sourceNode.buffer = audioBuffer;\n sourceNode.connect(this.#utteranceGain ?? context.destination);\n sourceNode.start(startAt);\n if (this.#sharedMode) this.#liveSources.add(sourceNode);\n await new Promise<void>((resolve) => {\n sourceNode.onended = () => {\n this.#liveSources.delete(sourceNode);\n resolve();\n };\n });\n }\n}\n","/**\n * Cartesia playback adapter.\n *\n * Imperative twin of `useCartesiaSpeaker.speak()`: token → WebSocket → send →\n * SinkAwarePlayer. Lives outside React so the singleton `playbackQueue` can\n * drive it. Output device routing is owned by the player itself — it applies\n * the preferred speaker at context creation and re-routes mid-utterance on\n * device change (see features/audio/sinkAwarePlayer.ts).\n *\n * Note: Cartesia \"speed\" is a synthesis-time parameter, so live rate changes are\n * not supported (no `setRate`). The queue's global rate is captured into the\n * synthesis `speed` at enqueue time by the consumer instead.\n */\n\nimport { parseMarkdownToText } from \"../voices/speech-text\";\nimport { resolveVoiceId, TTS_MODEL_ID, TTS_PLAYBACK_BUFFER_SEC, resolveSpeed, TTS_DEFAULT_VOLUME } from \"../voices/tts-config\";\nimport type { SpeechPronunciation } from \"../voices/speech-text\";\nimport { connectCartesiaTts } from \"./cartesia-socket\";\nimport { requirePort, speechPorts } from \"./configure\";\nimport { SinkAwarePlayer } from \"./sink-aware-player\";\nimport type { ActivePlayback, PlaybackAdapter, PlaybackAdapterCallbacks, PlaybackItem } from \"./types\";\n\n/** Cartesia generation config from the person's speed / emotion. */\nexport function buildGenerationConfig(opts?: {\n speed?: number | null;\n volume?: number | null;\n emotion?: string | null;\n}): { speed: number; volume: number; emotion?: string } {\n const cfg: { speed: number; volume: number; emotion?: string } = {\n speed: resolveSpeed(opts?.speed),\n volume: opts?.volume ?? TTS_DEFAULT_VOLUME,\n };\n if (opts?.emotion) cfg.emotion = opts.emotion.toLowerCase();\n return cfg;\n}\n\nasync function resolveText(item: PlaybackItem): Promise<string> {\n let pronunciations: SpeechPronunciation[] = [];\n // Custom pronunciations (the web's dictionary) are best-effort: a failure speaks without them.\n try {\n pronunciations = (await speechPorts().resolvePronunciations?.(item)) ?? [];\n } catch {\n pronunciations = [];\n }\n const processMarkdown = item.processMarkdown ?? true;\n return processMarkdown\n ? parseMarkdownToText(item.text, pronunciations.length ? { pronunciations } : undefined)\n : item.text;\n}\n\nexport const cartesiaAdapter: PlaybackAdapter = {\n provider: \"cartesia\",\n\n async start(\n item: PlaybackItem,\n cb: PlaybackAdapterCallbacks,\n ): Promise<ActivePlayback> {\n cb.onLoading();\n\n // START-time resolution: the item carries only the caller's EXPLICIT\n // overrides; everything else comes from the settings ladder (voice:\n // organization → user → device) and the tiered listening config (speed,\n // language) as they stand NOW — so queued items and history replays\n // honor settings changed after they were enqueued. Awaited: a cold\n // cache must never pick the default voice for a paid utterance.\n const overrides = item.cartesia ?? {};\n const settings = await requirePort(\"resolveVoiceSettings\")();\n const voice = {\n voiceId:\n overrides.voiceId ??\n resolveVoiceId(settings.voice, overrides.purpose ?? \"assistant\"),\n language: overrides.language ?? settings.language,\n speed: overrides.speed ?? settings.speed,\n emotion: settings.emotion,\n };\n\n const processed = (await resolveText(item)).trim();\n if (!processed) {\n throw new Error(\"Nothing to speak\");\n }\n\n // Auth + WebSocket via the single Cartesia connection chokepoint.\n const { ws } = await connectCartesiaTts();\n\n let stopped = false;\n const player = new SinkAwarePlayer({\n bufferDuration: TTS_PLAYBACK_BUFFER_SEC,\n });\n\n const cleanup = () => {\n try {\n ws.disconnect();\n } catch {\n /* already closed */\n }\n };\n\n const response = await ws.send({\n modelId: TTS_MODEL_ID,\n voice: { mode: \"id\" as const, id: voice.voiceId },\n language: (voice.language || \"en\") as never,\n transcript: processed,\n generationConfig: buildGenerationConfig({ speed: voice.speed, emotion: voice.emotion ?? null }) as never,\n });\n\n if (stopped) {\n cleanup();\n throw new Error(\"Playback cancelled before it started\");\n }\n\n cb.onPlaying();\n\n // play() resolves when playback FINISHES — do NOT await it here.\n player\n .play(response.source)\n .then(() => {\n if (stopped) return;\n cleanup();\n cb.onEnded();\n })\n .catch((err) => {\n if (stopped) return;\n cleanup();\n cb.onError(err instanceof Error ? err.message : \"Playback failed\");\n });\n\n return {\n pause: () => player.pause().catch(() => {}),\n resume: () => player.resume().catch(() => {}),\n stop: async () => {\n stopped = true;\n try {\n await player.stop();\n } catch {\n /* noop */\n }\n cleanup();\n },\n };\n },\n};\n","/**\n * @ai-matrx/media/speech — THE one speech engine (framework-free): `speak()`, the app-wide\n * playback queue + lock, the Cartesia streaming lane, the sink-aware player and the iOS/WebKit\n * output unlock. A host wires its token, voice settings and failure report once with\n * `configureSpeech`. React: `useSpeech` / `usePlaybackSnapshot` in `@ai-matrx/media/react`.\n */\nexport { configureSpeech, isSpeechConfigured, resetSpeechPortsForTests, type SpeechPorts, type SpeechVoiceSettings } from \"./speech/configure\";\nexport { speak, resolveSpeakEngine, type SpeakRequest, type SpeakResult } from \"./speech/speak\";\nexport {\n enqueuePlayback,\n pausePlayback,\n resumePlayback,\n skipPlayback,\n playPlaybackItem,\n removePlaybackItem,\n clearPlayback,\n setPlaybackRate,\n getPlaybackSnapshot,\n subscribePlayback,\n resetPlaybackForTests,\n type EnqueueResult,\n} from \"./speech/playback-queue\";\nexport * from \"./speech/playback-lock\";\nexport * from \"./speech/types\";\nexport * from \"./speech/unlock\";\nexport * from \"./speech/output-sink\";\nexport * from \"./speech/sink-aware-player\";\nexport {\n connectCartesiaTts,\n CartesiaAudioSource,\n ProviderSessionError,\n isCartesiaAuthError,\n type CartesiaTtsSocket,\n type CartesiaTtsSocketOptions,\n type CartesiaTtsRequest,\n type CartesiaTtsResponse,\n type CartesiaTtsVoice,\n type CartesiaConnectionCtx,\n} from \"./speech/cartesia-socket\";\nexport { cartesiaAdapter, buildGenerationConfig } from \"./speech/cartesia-adapter\";\n","/**\n * speak() — THE entry point for turning text into audio, in every app.\n *\n * One call, any engine. The caller says WHAT to say (and optionally which engine); the queue owns\n * one-at-a-time playback, the app-wide playback lock and lazy-loading of the engine. Cartesia's\n * voice / language / speed resolve at START time through the host's `resolveVoiceSettings` port,\n * so queued items and replays honor the settings current when audio plays. Framework-free;\n * React surfaces use `useSpeech()` (`@ai-matrx/media/react`) on top of the same call.\n */\n\nimport type { VoicePurpose } from \"../voices/tts-config\";\nimport { speechPorts } from \"./configure\";\nimport { enqueuePlayback } from \"./playback-queue\";\nimport type { PlaybackProvider, PlaybackRequest } from \"./types\";\n\nexport interface SpeakRequest {\n text: string;\n /** Engine to synthesize with. Omit for the host's default (Cartesia unless configured). */\n engine?: PlaybackProvider | undefined;\n /** Which voice slot to resolve for engines with per-purpose voices (Cartesia). */\n purpose?: VoicePurpose | undefined;\n /** Strip markdown before speaking (default true). */\n processMarkdown?: boolean | undefined;\n /** Short human label for a queue / Media panel row. */\n label?: string | undefined;\n /** Opt this utterance into Custom Dictionary pronunciation (host-resolved). */\n dictionarySurfaceKey?: string | undefined;\n /** Explicit voice id — overrides the saved voice for this utterance. */\n voice?: string | undefined;\n /** Explicit language — overrides the saved language. */\n language?: string | undefined;\n /** Explicit speed — overrides the saved speed. */\n speed?: number | undefined;\n /** Hear a catalog model's sample of one voice instead of speaking `text` (catalog engine). */\n sample?: { model: string; voice: string; offeringId?: string | undefined } | undefined;\n}\n\nexport interface SpeakResult {\n /** Queue item id — feed it to the queue's pause/resume/remove verbs. */\n id: string;\n /** The engine that actually ran (after defaulting). */\n engine: PlaybackProvider;\n}\n\n/** The engine for a request: explicit wins, otherwise the host default, otherwise Cartesia. */\nexport function resolveSpeakEngine(requested?: PlaybackProvider): PlaybackProvider {\n return requested ?? speechPorts().defaultEngine ?? \"cartesia\";\n}\n\n/** Speak text through the single playback queue. */\nexport function speak(request: SpeakRequest): SpeakResult {\n const complete = (r: PlaybackRequest): PlaybackRequest => speechPorts().completeRequest?.(r) ?? r;\n if (request.sample) {\n const { id } = enqueuePlayback(\n complete({\n text: request.text,\n processMarkdown: false,\n label: request.label,\n provider: \"catalog\",\n catalog: { sample: request.sample },\n }),\n );\n return { id, engine: \"catalog\" };\n }\n const engine = resolveSpeakEngine(request.engine);\n const common = {\n text: request.text,\n processMarkdown: request.processMarkdown,\n label: request.label,\n dictionarySurfaceKey: request.dictionarySurfaceKey,\n };\n if (engine === \"cartesia\") {\n // Only the caller's EXPLICIT overrides ride the item; the adapter resolves the rest at START.\n const { id } = enqueuePlayback(\n complete({\n ...common,\n provider: \"cartesia\",\n cartesia: {\n voiceId: request.voice,\n language: request.language,\n speed: request.speed,\n purpose: request.purpose ?? \"assistant\",\n },\n }),\n );\n return { id, engine };\n }\n const { id } = enqueuePlayback(complete({ ...common, provider: engine, catalog: { voice: request.voice } }));\n return { id, engine };\n}\n","/**\n * playbackQueue — the single, app-wide audio playback queue.\n *\n * Framework-free singleton (the playback twin of `captureLock`). Every \"speak\n * this\" request becomes an item; audio plays ONE at a time. New requests while\n * something is playing are appended (queued, never overlapped). Finished items\n * stay in history (status `done`) until cleared so the UI can offer replay.\n *\n * Providers plug in via `PlaybackAdapter`s, so the queue never knows about\n * Cartesia WebSockets or Groq blobs.\n *\n * Moved from matrx-frontend (features/audio/playback/playbackQueue.ts) into @ai-matrx/media so\n * every app plays through ONE queue. A host may mirror snapshots (the web mirrors into Redux for\n * its Media panel); React reads it through `useSpeech` / `usePlaybackSnapshot`.\n */\n\nimport { speechPorts } from \"./configure\";\nimport { primeAudioOutput } from \"./unlock\";\nimport { claimPlayback, releasePlayback } from \"./playback-lock\";\nimport type {\n ActivePlayback,\n PlaybackAdapter,\n PlaybackItem,\n PlaybackProvider,\n PlaybackRequest,\n PlaybackSnapshot,\n} from \"./types\";\n\n/** This queue's identity in the app-wide playback lock. */\nconst QUEUE_HOLDER_ID = \"playback-queue\";\n\n// Adapters are loaded lazily on first use so the heavy provider SDKs (Cartesia\n// WebSocket client, etc.) stay OUT of the app shell — this module is imported by\n// the always-mounted AudioPlaybackHost. Nothing TTS loads until the first\n// `speak`. Cached after first load (engage once, stay engaged).\nconst adapterCache = new Map<PlaybackProvider, PlaybackAdapter>();\n\nasync function getAdapter(\n provider: PlaybackProvider,\n): Promise<PlaybackAdapter> {\n const cached = adapterCache.get(provider);\n if (cached) return cached;\n let adapter: PlaybackAdapter;\n const registered = speechPorts().adapters?.[provider];\n if (registered) {\n adapter = await registered();\n } else if (provider === \"cartesia\") {\n adapter = (await import(\"./cartesia-adapter\")).cartesiaAdapter;\n } else {\n throw new Error(`No speech engine \"${provider}\" is set up in this app.`);\n }\n adapterCache.set(provider, adapter);\n return adapter;\n}\n\ntype Listener = (snapshot: PlaybackSnapshot) => void;\n\nlet items: PlaybackItem[] = [];\nlet currentId: string | null = null;\nlet rate = 1;\nlet active: ActivePlayback | null = null;\nlet runToken = 0;\nconst listeners = new Set<Listener>();\n\nlet seq = 0;\nfunction nextId(): string {\n seq += 1;\n return `pb_${Date.now().toString(36)}_${seq}`;\n}\n\nfunction snapshot(): PlaybackSnapshot {\n return { items: items.map((i) => ({ ...i })), currentId, rate };\n}\n\nfunction notify(): void {\n const snap = snapshot();\n listeners.forEach((l) => {\n try {\n l(snap);\n } catch (err) {\n console.error(\"[playbackQueue] listener error\", err);\n }\n });\n}\n\nfunction patch(id: string, changes: Partial<PlaybackItem>): void {\n items = items.map((i) => (i.id === id ? { ...i, ...changes } : i));\n}\n\nfunction isBusy(): boolean {\n return active !== null || currentId !== null;\n}\n\n/** Begin playing a specific item id, taking over any active playback. */\nasync function startItem(id: string): Promise<void> {\n const item = items.find((i) => i.id === id);\n if (!item) return;\n\n // Claim app-wide playback BEFORE producing audio — this stops any other\n // playback path (streaming auto-voice, etc.) so two voices can never overlap.\n // Re-claiming with our own id (item→item within the queue) is a no-op refresh.\n claimPlayback({\n id: QUEUE_HOLDER_ID,\n label: \"Playback queue\",\n stop: forcePreemptFromLock,\n });\n\n // Take over anything currently active.\n if (active) {\n const prev = active;\n active = null;\n try {\n await prev.stop();\n } catch {\n /* noop */\n }\n }\n\n const token = ++runToken;\n currentId = id;\n patch(id, { status: \"loading\", error: undefined });\n notify();\n\n const stale = () => token !== runToken;\n\n // The host's start gate (the web holds until an organization is set: speaking mints a provider\n // credential and reads the organization's listening knobs). A refusal fails this item only.\n const beforeStart = speechPorts().beforeStart;\n if (beforeStart) {\n try {\n await beforeStart(item);\n } catch (err) {\n if (stale()) return;\n patch(id, {\n status: \"error\",\n error:\n speechPorts().describeStartRefusal?.(err) ??\n (err instanceof Error ? err.message : \"Playback failed\"),\n });\n active = null;\n void advance();\n return;\n }\n if (stale()) return;\n }\n\n try {\n const adapter = await getAdapter(item.provider);\n if (stale()) return;\n const handle = await adapter.start(\n item,\n {\n onLoading: () => {\n if (stale()) return;\n patch(id, { status: \"loading\" });\n notify();\n },\n onPlaying: () => {\n if (stale()) return;\n patch(id, { status: \"playing\" });\n notify();\n },\n onEnded: () => {\n if (stale()) return;\n patch(id, { status: \"done\" });\n active = null;\n void advance();\n },\n onError: (message: string) => {\n if (stale()) return;\n patch(id, { status: \"error\", error: message });\n active = null;\n void advance();\n },\n },\n rate,\n );\n\n if (stale()) {\n // Taken over while we were connecting — discard this handle.\n try {\n await handle.stop();\n } catch {\n /* noop */\n }\n return;\n }\n active = handle;\n } catch (err) {\n if (stale()) return;\n patch(id, {\n status: \"error\",\n error: err instanceof Error ? err.message : \"Playback failed\",\n });\n active = null;\n void advance();\n }\n}\n\n/** Move to the next queued item, or go idle (releasing the playback lock). */\nasync function advance(): Promise<void> {\n const next = items.find((i) => i.status === \"queued\");\n if (next) {\n await startItem(next.id);\n } else {\n currentId = null;\n releasePlayback(QUEUE_HOLDER_ID);\n notify();\n }\n}\n\n/**\n * Called by the playback lock when ANOTHER path (e.g. streaming auto-voice)\n * takes over. Stop our audio and drop the queue — the user deliberately started\n * something else. Does NOT touch the lock (it already cleared us).\n */\nfunction forcePreemptFromLock(): void {\n runToken += 1;\n if (active) {\n const prev = active;\n active = null;\n try {\n void prev.stop();\n } catch {\n /* noop */\n }\n }\n items = [];\n currentId = null;\n notify();\n}\n\nexport interface EnqueueResult {\n id: string;\n}\n\n/** Add a request to the queue. Plays immediately if nothing is active. */\nexport function enqueuePlayback(request: PlaybackRequest): EnqueueResult {\n // First engagement mounts the lazy audio system (mirrors + panel UI). The\n // queue itself plays fine without it; the host replays state on mount.\n speechPorts().onEngage?.();\n // Manual enqueues run inside a tap — unlock iOS/WebKit output NOW, while\n // the gesture stack is live (audio itself starts after async work).\n primeAudioOutput();\n const id = nextId();\n const item: PlaybackItem = {\n ...request,\n id,\n status: \"queued\",\n enqueuedAtMs: Date.now(),\n };\n items = [...items, item];\n notify();\n if (!isBusy()) {\n void startItem(id);\n }\n return { id };\n}\n\nexport async function pausePlayback(): Promise<void> {\n if (!active || !currentId) return;\n await active.pause();\n patch(currentId, { status: \"paused\" });\n notify();\n}\n\nexport async function resumePlayback(): Promise<void> {\n speechPorts().onEngage?.();\n primeAudioOutput();\n if (!active || !currentId) return;\n await active.resume();\n patch(currentId, { status: \"playing\" });\n notify();\n}\n\n/** Stop the active item (marks it done) and advance to the next queued item. */\nexport async function skipPlayback(): Promise<void> {\n if (currentId) patch(currentId, { status: \"done\" });\n if (active) {\n const prev = active;\n active = null;\n try {\n await prev.stop();\n } catch {\n /* noop */\n }\n }\n await advance();\n}\n\n/** Play (or replay) a specific item now, taking over anything active. */\nexport async function playPlaybackItem(id: string): Promise<void> {\n speechPorts().onEngage?.();\n primeAudioOutput();\n const item = items.find((i) => i.id === id);\n if (!item) return;\n // Replay finished/errored items by resetting them to queued.\n if (item.status === \"done\" || item.status === \"error\") {\n patch(id, { status: \"queued\", error: undefined });\n }\n if (currentId && currentId !== id) {\n patch(currentId, { status: \"done\" });\n }\n await startItem(id);\n}\n\n/** Remove an item. If it's the active one, advance. */\nexport async function removePlaybackItem(id: string): Promise<void> {\n if (id === currentId) {\n await skipPlayback();\n items = items.filter((i) => i.id !== id);\n notify();\n return;\n }\n items = items.filter((i) => i.id !== id);\n notify();\n}\n\n/** Stop everything and clear the queue + history. */\nexport async function clearPlayback(): Promise<void> {\n runToken += 1;\n if (active) {\n const prev = active;\n active = null;\n try {\n await prev.stop();\n } catch {\n /* noop */\n }\n }\n items = [];\n currentId = null;\n releasePlayback(QUEUE_HOLDER_ID);\n notify();\n}\n\nexport function setPlaybackRate(value: number): void {\n rate = Math.max(0.25, Math.min(4, value));\n active?.setRate?.(rate);\n notify();\n}\n\nexport function getPlaybackSnapshot(): PlaybackSnapshot {\n return snapshot();\n}\n\n/** Test seam: drop every item and forget loaded engines. */\nexport function resetPlaybackForTests(): void {\n runToken += 1;\n active = null;\n items = [];\n currentId = null;\n rate = 1;\n adapterCache.clear();\n releasePlayback(QUEUE_HOLDER_ID);\n}\n\nexport function subscribePlayback(listener: Listener): () => void {\n listeners.add(listener);\n listener(snapshot());\n return () => {\n listeners.delete(listener);\n };\n}\n","/**\n * Playback Lock — app-wide single-OUTPUT arbitration.\n *\n * The output twin of `captureLock`. Just as only one mic capture may be live at\n * a time, **only one thing may be producing audible playback at any instant,\n * anywhere in the app.** Multiple playback paths exist (the unified\n * `playbackQueue` for Speaker buttons, the streaming auto-voice\n * `useCartesiaStreamingSpeaker`, xAI realtime, podcast PCM) and each one used to\n * play independently — so a War Room read-aloud could stream over a queued\n * utterance, two voices in your ear at once. This arbiter makes that\n * structurally impossible.\n *\n * Claiming is **start-always-wins**: a new claim instantly stops the current\n * holder (its `stop` runs synchronously) and takes ownership. Framework-free,\n * allocates nothing until first use, holds no audio itself — it only tracks who\n * currently owns playback and how to stop them.\n *\n * Contract for holders (mirror of captureLock):\n * - Stable id per path (constant for a singleton owner, `useId()` per instance).\n * - `claimPlayback({ id, stop })` right before producing audio.\n * - `releasePlayback(id)` when playback ends (finished, stopped, errored,\n * unmounted). Release is id-guarded — a stale release is a safe no-op.\n * - `stop` MUST be synchronous-effective (stop audio now).\n *\n * Subscribers get a `PlaybackTakeover` describing the holder change, which the\n * AudioPlaybackHost uses to surface the Audio panel whenever playback gets\n * \"complex\" (a cross-path takeover).\n */\n\nexport interface PlaybackHolder {\n /** Stable identifier for the claiming playback path / instance. */\n id: string;\n /** Optional human label for diagnostics / the panel indicator. */\n label?: string;\n /** Stop audio immediately. Called when another path takes over. */\n stop: () => void;\n}\n\nexport interface PlaybackTakeover {\n /** The holder that now owns playback, or null when playback went idle. */\n current: PlaybackHolder | null;\n /** The holder that was stopped to grant ownership, if this was a takeover. */\n preempted: PlaybackHolder | null;\n}\n\nlet current: PlaybackHolder | null = null;\nconst listeners = new Set<(event: PlaybackTakeover) => void>();\n\nfunction notify(preempted: PlaybackHolder | null): void {\n const event: PlaybackTakeover = { current, preempted };\n for (const cb of listeners) {\n try {\n cb(event);\n } catch (err) {\n console.error(\"[playbackLock] listener threw:\", err);\n }\n }\n}\n\n/**\n * Claim exclusive playback (start-always-wins). If a different holder currently\n * owns playback, its `stop()` runs first (synchronously) so two outputs can\n * never overlap. Re-claiming with the SAME id just refreshes the registration.\n */\nexport function claimPlayback(holder: PlaybackHolder): void {\n if (current && current.id === holder.id) {\n current = holder;\n notify(null);\n return;\n }\n let preempted: PlaybackHolder | null = null;\n if (current) {\n preempted = current;\n // Clear before stopping so a re-entrant claim/release from inside the\n // previous holder's stop() can't see a stale owner or loop.\n current = null;\n try {\n preempted.stop();\n } catch (err) {\n console.error(\n `[playbackLock] previous holder \"${preempted.id}\" stop() threw during takeover:`,\n err,\n );\n }\n }\n current = holder;\n notify(preempted);\n}\n\n/**\n * Release playback for `id`. Id-guarded: if `id` is not the current owner (it\n * was already taken over), this is a no-op.\n */\nexport function releasePlayback(id: string): void {\n if (current?.id === id) {\n current = null;\n notify(null);\n }\n}\n\n/** The id of the path currently holding playback, or null if idle. */\nexport function getActivePlaybackHolderId(): string | null {\n return current?.id ?? null;\n}\n\n/** True while any path holds playback. */\nexport function isPlaybackHeld(): boolean {\n return current !== null;\n}\n\n/** Subscribe to playback ownership changes. Returns an unsubscribe fn. */\nexport function subscribePlaybackLock(\n cb: (event: PlaybackTakeover) => void,\n): () => void {\n listeners.add(cb);\n return () => {\n listeners.delete(cb);\n };\n}\n"],"mappings":"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","names":["el","listeners","source","listeners","notify","id"]}
1
+ {"version":3,"sources":["../src/speech/configure.ts","../src/speech/unlock.ts","../src/voices/speech-text.ts","../src/voices/tts-config.ts","../src/speech/cartesia-socket.ts","../src/speech/output-sink.ts","../src/speech/sink-aware-player.ts","../src/speech/cartesia-adapter.ts","../src/speech.ts","../src/speech/speak.ts","../src/speech/playback-queue.ts","../src/speech/playback-lock.ts"],"sourcesContent":["/**\n * configureSpeech — the host's ports into THE one speech engine.\n *\n * The engine (queue, lock, Cartesia lane, sink-aware player, iOS unlock) is the same in every app;\n * what differs per host is only how it gets a provider token, which voice/speed/language the\n * person chose, how a provider refusal is reported, and which extra engines it registers. A host\n * calls `configureSpeech` once at start-up (before the first `speak`); later calls merge.\n */\n\nimport type {\n ProviderSessionFailure,\n ProviderSessionFailureVerdict,\n ProviderSessionUsage,\n} from \"@ai-matrx/agents/matrx\";\nimport type { SpeechPronunciation } from \"../voices/speech-text\";\nimport type { PlaybackAdapter, PlaybackItem, PlaybackProvider } from \"./types\";\n\n/** Voice parameters for one Cartesia utterance, resolved at START time. */\nexport interface SpeechVoiceSettings {\n /** The person's saved Cartesia voice id (null → the purpose default). */\n voice: string | null;\n language: string;\n speed: number;\n emotion?: string | null;\n}\n\nexport interface SpeechPorts {\n /** A short-lived Cartesia access token. `forceRefresh` after the provider rejected the last one. */\n getCartesiaToken: (opts?: { forceRefresh?: boolean }) => Promise<string>;\n /** Drop a token the provider rejected so the next read mints a fresh one. */\n invalidateCartesiaToken?: (token: string) => void;\n /** The person's voice / language / speed right now (tiered settings on the web). */\n resolveVoiceSettings: () => Promise<SpeechVoiceSettings>;\n /** Custom pronunciations for one utterance (best-effort; failures speak without them). */\n resolvePronunciations?: (item: PlaybackItem) => Promise<SpeechPronunciation[]>;\n /**\n * Report a browser-held provider session failure to the server; the verdict carries the\n * person's sentence and whether retrying can help.\n */\n reportProviderFailure?: (failure: ProviderSessionFailure) => Promise<ProviderSessionFailureVerdict | null>;\n /**\n * Report what a browser-held provider session spent (`POST /broker/usage`, once per `usage_id`).\n * The host owns retry and delivery; it must never throw.\n */\n reportProviderUsage?: (usage: ProviderSessionUsage) => void;\n /** Tell the person speech stopped (a toast on the web). Without it the error is only on the item. */\n notifyError?: (title: string, description: string) => void;\n /**\n * Runs before an item starts (e.g. \"speaking happens in an organization\" — hold until one is set).\n * Throwing marks the item failed with `describeStartRefusal(err)` (or the error's message).\n */\n beforeStart?: (item: PlaybackItem) => Promise<void>;\n describeStartRefusal?: (error: unknown) => string | null;\n /** First engagement of the engine (the web mounts its lazy audio UI here). */\n onEngage?: () => void;\n /** Extra engines (the web's server catalog lane). Cartesia is built in. */\n adapters?: Partial<Record<PlaybackProvider, () => Promise<PlaybackAdapter>>>;\n /** Engine used when a `speak` call names none. Default \"cartesia\". */\n defaultEngine?: PlaybackProvider;\n /** Fill engine-specific params a `speak` call left out (the web's catalog voice preference). */\n completeRequest?: (request: import(\"./types\").PlaybackRequest) => import(\"./types\").PlaybackRequest;\n}\n\nlet ports: Partial<SpeechPorts> = {};\n\nexport function configureSpeech(next: Partial<SpeechPorts>): void {\n ports = { ...ports, ...next, adapters: { ...ports.adapters, ...next.adapters } };\n}\n\n/** Read the configured ports (engine-internal). */\nexport function speechPorts(): Partial<SpeechPorts> {\n return ports;\n}\n\n/**\n * True when a host already wired `key` (default: the Cartesia token). A package that ships a\n * bare-host default (chat's `BareHostChrome`) fills only what nobody set, never overriding the host.\n */\nexport function isSpeechConfigured(key: keyof SpeechPorts = \"getCartesiaToken\"): boolean {\n return ports[key] !== undefined;\n}\n\n/** A port the engine cannot speak without. Throws a sentence a developer can act on. */\nexport function requirePort<K extends keyof SpeechPorts>(key: K): NonNullable<SpeechPorts[K]> {\n const value = ports[key];\n if (!value) {\n throw new Error(`Speech is not set up in this app: configureSpeech({ ${String(key)} }) was never called.`);\n }\n return value as NonNullable<SpeechPorts[K]>;\n}\n\n/** Test seam: forget every port. */\nexport function resetSpeechPortsForTests(): void {\n ports = {};\n}\n","/**\n * unlock.ts — THE iOS/WebKit audio-output unlock primitive.\n *\n * WHY THIS EXISTS (mobile-silence class, 2026-08-30)\n * --------------------------------------------------\n * On iOS every browser is WebKit (Chrome and Firefox on iPhone included), and\n * WebKit enforces two rules desktop browsers don't:\n *\n * 1. An `AudioContext` created (or resumed) OUTSIDE a user gesture starts\n * `suspended` and stays silent. Our TTS starts audio from websocket\n * callbacks seconds after the tap, so every utterance scheduled into a\n * fresh per-utterance context played into a suspended context — total\n * silence on iPhone while desktop worked fine.\n * 2. Web Audio output is muted by the ringer/silent switch unless the page\n * declares a playback audio session (`navigator.audioSession.type =\n * \"playback\"`, iOS 16.4+).\n *\n * THE FIX: capture a real user gesture ONCE, and inside it (synchronously)\n * - declare the playback audio session,\n * - create ONE shared `AudioContext`, resume it, and play a one-frame\n * silent buffer through it (the classic unlock), and\n * - prime ONE shared `HTMLAudioElement` with a muted play() so element\n * playback (the catalog engine) is also user-activated.\n *\n * Consumers then REUSE the shared unlocked context/element instead of minting\n * fresh ones outside a gesture:\n * - `SinkAwarePlayer` schedules through `getUnlockedAudioContext()` when it\n * exists (per-utterance GainNode; the context is never closed).\n * - `catalogAdapter` plays through `getPrimedMediaElement()` when it exists.\n *\n * Two entry points, both idempotent and framework-free:\n * - `installAudioUnlockListeners()` — capture-phase pointerdown/keydown\n * listeners that unlock on the user's FIRST interaction anywhere. Mounted\n * once at app root (AudioPlaybackHost), so by the time any audio plays,\n * the page is almost always already unlocked.\n * - `primeAudioOutput()` — explicit call at the top of every audio-starting\n * gesture handler (speak(), the Listen actions, transport buttons) as the\n * belt-and-suspenders for taps the global listener could miss.\n *\n * Everything feature-detects: on desktop this is a no-op-cost shared context\n * that behaves identically to before.\n *\n * THE CATEGORY IS SEQUENCED, NOT SHARED (2026-09-17)\n * -------------------------------------------------\n * `\"playback\"` is the OUTPUT category, and WebKit refuses every\n * `getUserMedia({audio})` under it (\"AudioSession category is not compatible\n * with audio capture\" — the /chat voice-input failure on iPhone). Capture needs\n * `\"play-and-record\"`. This file never touches capture: the package's mic\n * manager (`@ai-matrx/browser-audio` ≥ 0.4.0) switches the category right\n * before its own capture and restores whatever this file declared once the\n * microphone stops. Never make this file \"smarter\" about capture, and never\n * declare the category from a recording surface — one owner per direction.\n */\n\nlet sharedContext: AudioContext | null = null;\nlet sharedElement: HTMLAudioElement | null = null;\nlet listenersInstalled = false;\nlet audioSessionDeclared = false;\n\n/** One-frame silent WAV — enough to user-activate an HTMLAudioElement. */\nconst SILENT_WAV =\n \"data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=\";\n\nfunction declarePlaybackAudioSession(): void {\n if (audioSessionDeclared) return;\n // iOS 16.4+ — makes Web Audio respect \"media\" volume instead of being\n // muted by the ringer/silent switch. Absent elsewhere; feature-detected.\n const session = (\n navigator as Navigator & { audioSession?: { type: string } }\n ).audioSession;\n if (session) {\n try {\n session.type = \"playback\";\n audioSessionDeclared = true;\n } catch {\n /* older WebKit throws on unknown types — playback then follows ringer */\n }\n } else {\n audioSessionDeclared = true; // nothing to declare on this platform\n }\n}\n\n/**\n * The ONE shared output context, unlocked by a user gesture. Null until the\n * first `primeAudioOutput()` (or global-listener) gesture ran.\n */\nexport function getUnlockedAudioContext(): AudioContext | null {\n return sharedContext && sharedContext.state !== \"closed\"\n ? sharedContext\n : null;\n}\n\n/**\n * The ONE shared media element, user-activated by a muted play(). Null until\n * primed. Reused by element-based playback so `.play()` outside a gesture is\n * allowed on WebKit (activation is per-element).\n */\nexport function getPrimedMediaElement(): HTMLAudioElement | null {\n return sharedElement;\n}\n\n/**\n * Unlock audio output. MUST be called synchronously inside a user-gesture\n * call stack to have its full effect; safe (and cheap) to call any time.\n */\nexport function primeAudioOutput(): void {\n if (typeof window === \"undefined\") return;\n declarePlaybackAudioSession();\n\n // ── Web Audio ────────────────────────────────────────────────────────────\n try {\n if (!sharedContext || sharedContext.state === \"closed\") {\n sharedContext = new AudioContext();\n }\n if (sharedContext.state === \"suspended\") {\n void sharedContext.resume().catch(() => {\n /* outside a gesture this may reject — the next gesture retries */\n });\n }\n // The classic unlock: one silent frame through the graph. Repeat-safe.\n const buffer = sharedContext.createBuffer(1, 1, sharedContext.sampleRate);\n const node = sharedContext.createBufferSource();\n node.buffer = buffer;\n node.connect(sharedContext.destination);\n node.start(0);\n } catch {\n // No Web Audio (ancient browser) — element playback may still work.\n }\n\n // ── Media element ────────────────────────────────────────────────────────\n try {\n if (!sharedElement) {\n const el = new Audio();\n el.setAttribute(\"playsinline\", \"true\");\n sharedElement = el;\n }\n const el = sharedElement;\n // Only prime while idle — never disturb real playback on the element.\n if (el.paused && !el.src.startsWith(\"blob:\") && !el.currentSrc.startsWith(\"http\")) {\n el.muted = true;\n el.src = SILENT_WAV;\n void el\n .play()\n .then(() => {\n el.pause();\n el.muted = false;\n })\n .catch(() => {\n el.muted = false;\n });\n }\n } catch {\n /* priming is best-effort */\n }\n}\n\n/**\n * Install capture-phase first-interaction listeners that unlock audio on ANY\n * tap/keypress. Idempotent; the listeners stay installed (re-priming after\n * the OS suspends the page costs nothing and heals `interrupted` contexts).\n */\nexport function installAudioUnlockListeners(): void {\n if (listenersInstalled || typeof window === \"undefined\") return;\n listenersInstalled = true;\n const onGesture = () => primeAudioOutput();\n window.addEventListener(\"pointerdown\", onGesture, {\n capture: true,\n passive: true,\n });\n window.addEventListener(\"keydown\", onGesture, {\n capture: true,\n passive: true,\n });\n // iOS fires touchend-based activation more reliably than pointerdown for\n // some gestures (fast taps during scroll deceleration).\n window.addEventListener(\"touchend\", onGesture, {\n capture: true,\n passive: true,\n });\n}\n","/**\n * Speech text — THE one markdown → spoken-text conversion for read-aloud (TTS) on every client.\n * Moved from matrx-frontend (utils/markdown-processors/parse-markdown-for-speech) with the\n * extension's copy deleted (agents-chat W1, A7: TTS → media).\n */\nimport {\n findTableEnd,\n isPipeLedRow,\n replaceFences,\n rowCells,\n tableStartsAt,\n unescapeCellPipes,\n unwrapCodeSpans,\n} from \"@ai-matrx/content-ir/source\";\n\n/**\n * Every table — with or without edge pipes (THE GFM rule, gfm-table-lines) —\n * becomes one spoken sentence naming its row count and columns.\n */\nfunction speakTables(text: string): string {\n const lines = text.split(\"\\n\");\n const out: string[] = [];\n for (let i = 0; i < lines.length; i += 1) {\n if (!tableStartsAt(lines, i)) {\n out.push(lines[i] ?? \"\");\n continue;\n }\n const end = findTableEnd(lines, i);\n const headers = rowCells(lines[i] ?? \"\").map(unescapeCellPipes).filter((h) => h.length > 0);\n const rowCount = lines.slice(i + 2, end).filter((line) => line.trim().length > 1).length;\n const headerText =\n headers.length > 1\n ? headers.slice(0, -1).join(\", \") + \", and \" + headers[headers.length - 1]\n : headers.length === 1\n ? headers[0]\n : \"unlabeled columns\";\n const rowText = rowCount === 1 ? \"one row\" : rowCount > 0 ? `${rowCount} rows` : \"no rows\";\n out.push(`There is a table with ${rowText} of data provided for ${headerText}.`);\n i = end - 1;\n }\n return out.join(\"\\n\");\n}\n\n/** Languages a listener hears by name (\"Please see the python code provided.\"). */\nconst SPOKEN_CODE_LANGUAGES: ReadonlySet<string> = new Set([\n \"javascript\", \"js\", \"typescript\", \"ts\", \"python\", \"py\", \"java\", \"csharp\", \"cs\", \"cpp\", \"c++\", \"c\",\n \"go\", \"rust\", \"php\", \"ruby\", \"swift\", \"kotlin\", \"scala\", \"sql\", \"bash\", \"shell\", \"powershell\",\n \"yaml\", \"yml\", \"json\", \"xml\", \"html\", \"css\", \"markdown\", \"md\",\n]);\n/** A term→spoken-form substitution applied before any other speech parsing. */\nexport interface SpeechPronunciation {\n from: string;\n to: string;\n}\n\nexport interface ParseMarkdownToTextOptions {\n /**\n * Custom Dictionary pronunciation substitutions. Applied FIRST — before the\n * built-in abbreviation/number normalization — so a user term like \"AI\" can\n * supply its own spoken form instead of the generic \"A I\" readout. Pairs\n * should be sorted longest-first by the caller so multi-word terms replace\n * before substrings.\n */\n pronunciations?: SpeechPronunciation[];\n}\n\n/**\n * Long-form meanings retained as reference data for search, display, or future\n * accessibility features. TTS must not use these expansions: repeatedly\n * reading \"Application Programming Interface\" for API is not natural speech.\n */\nexport const COMMON_ABBREVIATION_EXPANSIONS = {\n AI: \"Artificial Intelligence\",\n API: \"Application Programming Interface\",\n HTTP: \"Hypertext Transfer Protocol\",\n HTTPS: \"Hypertext Transfer Protocol Secure\",\n URL: \"Uniform Resource Locator\",\n URI: \"Uniform Resource Identifier\",\n JSON: \"JavaScript Object Notation\",\n XML: \"eXtensible Markup Language\",\n CSS: \"Cascading Style Sheets\",\n HTML: \"Hypertext Markup Language\",\n JS: \"JavaScript\",\n TS: \"TypeScript\",\n SQL: \"Structured Query Language\",\n DB: \"Database\",\n UI: \"User Interface\",\n UX: \"User Experience\",\n SEO: \"Search Engine Optimization\",\n SDK: \"Software Development Kit\",\n CLI: \"Command Line Interface\",\n IDE: \"Integrated Development Environment\",\n JWT: \"JSON Web Token\",\n OAuth: \"Open Authorization\",\n REST: \"Representational State Transfer\",\n CRUD: \"Create Read Update Delete\",\n MVC: \"Model View Controller\",\n SPA: \"Single Page Application\",\n SSR: \"Server Side Rendering\",\n CSR: \"Client Side Rendering\",\n PWA: \"Progressive Web App\",\n DOM: \"Document Object Model\",\n BOM: \"Browser Object Model\",\n CDN: \"Content Delivery Network\",\n CMS: \"Content Management System\",\n ERP: \"Enterprise Resource Planning\",\n CRM: \"Customer Relationship Management\",\n SaaS: \"Software as a Service\",\n PaaS: \"Platform as a Service\",\n IaaS: \"Infrastructure as a Service\",\n VPN: \"Virtual Private Network\",\n LAN: \"Local Area Network\",\n WAN: \"Wide Area Network\",\n TCP: \"Transmission Control Protocol\",\n UDP: \"User Datagram Protocol\",\n IP: \"Internet Protocol\",\n DNS: \"Domain Name System\",\n FTP: \"File Transfer Protocol\",\n SMTP: \"Simple Mail Transfer Protocol\",\n POP3: \"Post Office Protocol version 3\",\n IMAP: \"Internet Message Access Protocol\",\n} as const;\n\ntype CommonAbbreviation = keyof typeof COMMON_ABBREVIATION_EXPANSIONS;\n\n/** Escape a string for safe use inside a RegExp. */\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst COMMON_ABBREVIATION_BY_UPPERCASE = Object.fromEntries(\n (Object.keys(COMMON_ABBREVIATION_EXPANSIONS) as CommonAbbreviation[]).map(\n (abbreviation) => [abbreviation.toUpperCase(), abbreviation],\n ),\n) as Record<string, CommonAbbreviation>;\n\nconst COMMON_ABBREVIATION_PATTERN = new RegExp(\n `\\\\b(${Object.keys(COMMON_ABBREVIATION_EXPANSIONS)\n .sort((a, b) => b.length - a.length)\n .map(escapeRegExp)\n .join(\"|\")})(['’]s|s)?\\\\b`,\n \"gi\",\n);\n\n/** Acronyms normally spoken as words; leave them in conventional written form. */\nconst SPOKEN_AS_WORD = new Set<CommonAbbreviation>([\n \"JSON\",\n \"OAuth\",\n \"REST\",\n \"CRUD\",\n \"DOM\",\n \"BOM\",\n \"SaaS\",\n \"PaaS\",\n \"IaaS\",\n \"LAN\",\n \"WAN\",\n]);\n\n/**\n * These acronyms are also ordinary English words when lowercased. Only their\n * conventional acronym casing is safe to normalize.\n */\nconst CASE_SENSITIVE_WORD_ACRONYMS = new Set<CommonAbbreviation>([\n \"REST\",\n \"CRUD\",\n \"SPA\",\n \"DOM\",\n \"BOM\",\n \"LAN\",\n \"WAN\",\n]);\n\nfunction spokenFormForAbbreviation(\n match: string,\n sourceAbbreviation: string,\n suffix = \"\",\n): string {\n const abbreviation =\n COMMON_ABBREVIATION_BY_UPPERCASE[sourceAbbreviation.toUpperCase()];\n if (!abbreviation) return match;\n if (\n CASE_SENSITIVE_WORD_ACRONYMS.has(abbreviation) &&\n sourceAbbreviation !== abbreviation\n ) {\n return match;\n }\n if (SPOKEN_AS_WORD.has(abbreviation)) return `${abbreviation}${suffix}`;\n const spoken = Array.from(abbreviation.toUpperCase()).join(\" \");\n // Plural and possessive initialisms sound the same. The apostrophe gives\n // both engines an unambiguous suffix instead of turning the final \"s\" into\n // another letter in the initialism.\n return suffix ? `${spoken}'s` : spoken;\n}\n\n/**\n * Normalize technical abbreviations for both active TTS lanes.\n *\n * Cartesia Sonic 3.5 recommends single-space character delimiters for forced\n * readout (\"A P I\"), and that plain-text form also works with the catalog's\n * Groq/Orpheus engine, which has no SSML/pronunciation-dictionary input. Word\n * acronyms stay conventional so models can say \"json\", \"oh-auth\", etc.\n */\nexport function normalizeSpeechAbbreviations(text: string): string {\n if (!text) return text;\n return text.replace(COMMON_ABBREVIATION_PATTERN, spokenFormForAbbreviation);\n}\n\n/**\n * The spoken word a fill-in-the-blank run of underscores becomes. One constant\n * so every speech surface reads the same thing — change it here (e.g. \"what\")\n * and TTS + generated flashcard fronts follow.\n */\nexport const SPEECH_BLANK_WORD = \"blank\";\n\n/**\n * Fill-in-the-blank underscores → a spoken word. A run of two-or-more\n * underscores (any length — \"___\", \"_____\"), including the spaced style\n * (\"_ _ _\"), reads as \"blank\" instead of the literal \"underscore\" every TTS\n * engine would otherwise say. Generic and reusable: the centralized speech\n * cleaner AND the generated flashcard spoken-fronts both route through this, so\n * a blank sounds identical everywhere.\n *\n * Guards (so it only ever touches real blanks):\n * - bounded by non-alphanumerics → never mangles `snake_case` or `__dunder__`;\n * - a line of ONLY underscores is a markdown thematic break (divider), left\n * untouched here for the horizontal-rule rule to drop.\n */\nexport function normalizeSpeechBlanks(text: string): string {\n if (!text || !text.includes(\"_\")) return text;\n const blank = /(?<![\\p{L}\\p{N}])_(?:[ \\t]*_)+(?![\\p{L}\\p{N}])/gu;\n return text\n .split(\"\\n\")\n .map((line) =>\n /^[ \\t]*_[ \\t_]*$/.test(line)\n ? line // whole-line underscores = divider, not a blank\n : line.replace(blank, ` ${SPEECH_BLANK_WORD} `),\n )\n .join(\"\\n\");\n}\n\n/** Apply dictionary pronunciation substitutions, whole-word & case-insensitive. */\nfunction applyPronunciations(\n text: string,\n pairs: SpeechPronunciation[],\n): string {\n let out = text;\n for (const { from, to } of pairs) {\n const term = from.trim();\n if (!term || !to) continue;\n // \\b only anchors on word chars; for terms with leading/trailing non-word\n // chars fall back to a lookaround on whitespace/boundaries.\n const re = new RegExp(\n `(?<![\\\\p{L}\\\\p{N}])${escapeRegExp(term)}(?![\\\\p{L}\\\\p{N}])`,\n \"giu\",\n );\n out = out.replace(re, to);\n }\n return out;\n}\n\nexport function parseMarkdownToText(\n markdown: string,\n options?: ParseMarkdownToTextOptions,\n): string {\n if (!markdown || typeof markdown !== \"string\") return \"\";\n\n const numberToWords = (num: string): string => {\n const ones = [\n \"zero\",\n \"one\",\n \"two\",\n \"three\",\n \"four\",\n \"five\",\n \"six\",\n \"seven\",\n \"eight\",\n \"nine\",\n \"ten\",\n \"eleven\",\n \"twelve\",\n \"thirteen\",\n \"fourteen\",\n \"fifteen\",\n \"sixteen\",\n \"seventeen\",\n \"eighteen\",\n \"nineteen\",\n ];\n const tens = [\n \"\",\n \"\",\n \"twenty\",\n \"thirty\",\n \"forty\",\n \"fifty\",\n \"sixty\",\n \"seventy\",\n \"eighty\",\n \"ninety\",\n ];\n const numInt = parseInt(num, 10);\n if (numInt < 20) return ones[numInt] ?? num;\n if (numInt < 100) {\n const t = Math.floor(numInt / 10);\n const o = numInt % 10;\n return o === 0 ? (tens[t] ?? num) : `${tens[t] ?? \"\"}-${ones[o] ?? \"\"}`;\n }\n if (numInt < 1000) {\n const h = Math.floor(numInt / 100);\n const remainder = numInt % 100;\n return remainder === 0\n ? `${ones[h]} hundred`\n : `${ones[h]} hundred ${numberToWords(String(remainder))}`;\n }\n return String(numInt);\n };\n\n const emojiMap: { [key: string]: string } = {\n \"😊\": \"smiling face\",\n \"😂\": \"laughing face\",\n \"❤️\": \"heart\",\n \"👍\": \"thumbs up\",\n \"👎\": \"thumbs down\",\n \"🔥\": \"fire\",\n \"⭐\": \"star\",\n \"✅\": \"check mark\",\n \"❌\": \"cross mark\",\n \"⚠️\": \"warning\",\n \"🚀\": \"rocket\",\n \"💡\": \"light bulb\",\n \"🎯\": \"target\",\n \"📱\": \"mobile phone\",\n \"💻\": \"laptop\",\n \"🌟\": \"glowing star\",\n \"🔗\": \"link\",\n \"📧\": \"email\",\n \"📄\": \"document\",\n \"📊\": \"chart\",\n \"🛠️\": \"tools\",\n \"⚙️\": \"gear\",\n \"🔧\": \"wrench\",\n \"📈\": \"chart increasing\",\n \"📉\": \"chart decreasing\",\n \"🎉\": \"party popper\",\n \"💪\": \"flexed biceps\",\n \"🤔\": \"thinking face\",\n \"💭\": \"thought bubble\",\n \"👀\": \"eyes\",\n \"👋\": \"waving hand\",\n \"✨\": \"sparkles\",\n \"🔍\": \"magnifying mx-glass\",\n \"📝\": \"memo\",\n \"📋\": \"clipboard\",\n \"📅\": \"calendar\",\n \"⏰\": \"alarm clock\",\n \"🔒\": \"locked\",\n \"🔓\": \"unlocked\",\n \"🏆\": \"trophy\",\n \"🎖️\": \"military medal\",\n \"🎗️\": \"reminder ribbon\",\n \"🏅\": \"sports medal\",\n \"🥇\": \"first place medal\",\n \"🥈\": \"second place medal\",\n \"🥉\": \"third place medal\",\n };\n\n const measurementUnits: { [key: string]: string } = {\n // Weight\n lbs: \"pound\", // Changed from 'pounds'\n lb: \"pound\",\n oz: \"ounce\", // Changed from 'ounces'\n kg: \"kilogram\", // Changed from 'kilograms'\n gm: \"gram\", // Changed from 'grams'\n mg: \"milligram\", // Changed from 'milligrams'\n // Distance/Length\n km: \"kilometer\", // Changed from 'kilometers'\n cm: \"centimeter\", // Changed from 'centimeters'\n mm: \"millimeter\", // Changed from 'millimeters'\n ft: \"foot\", // Changed from 'feet' (Irregular plural)\n mi: \"mile\", // Changed from 'miles'\n // Speed\n mph: \"mile per hour\", // Changed from 'miles per hour'\n kph: \"kilometer per hour\", // Changed from 'kilometers per hour'\n kmh: \"kilometer per hour\", // Changed from 'kilometers per hour'\n // Volume\n ml: \"milliliter\", // Changed from 'milliliters'\n mL: \"milliliter\", // Changed from 'milliliters'\n gal: \"gallon\", // Changed from 'gallons'\n qt: \"quart\", // Changed from 'quarts'\n tbsp: \"tablespoon\", // Changed from 'tablespoons'\n tsp: \"teaspoon\", // Changed from 'teaspoons'\n // Area\n sqft: \"square foot\", // Changed from 'square feet' (Irregular plural)\n sqm: \"square meter\", // Changed from 'square meters'\n // Time\n sec: \"second\", // Changed from 'seconds'\n min: \"minute\", // Changed from 'minutes'\n hr: \"hour\", // Changed from 'hours'\n hrs: \"hour\", // Changed from 'hours'\n ms: \"millisecond\", // Changed from 'milliseconds'\n // Other common units\n psi: \"pound per square inch\", // Changed from 'pounds per square inch'\n rpm: \"revolution per minute\", // Changed from 'revolutions per minute'\n bpm: \"beat per minute\", // Changed from 'beats per minute'\n };\n\n const stripReasoningTags = (input: string): string => {\n // Remove <thinking>...</thinking> and <reasoning>...</reasoning> blocks entirely,\n // including multi-line content. Also removes any stray unclosed opening tags.\n return input\n .replace(/<(thinking|reasoning|think|reason)\\b[^>]*>[\\s\\S]*?<\\/\\1>/gi, \"\")\n .replace(/<\\/?(?:thinking|reasoning|think|reason)\\b[^>]*>/gi, \"\");\n };\n\n const stripLeadingMarkdown = (input: string): string => {\n let text = input.replace(/^\\uFEFF/, \"\").replace(/^[ \\t]*\\r?\\n/, \"\");\n // Strip a single leading markdown marker at the very start so the first\n // line reads naturally (e.g. \"## Important notice\" -> \"Important notice\").\n // Only strips ONE marker — later occurrences are handled normally below.\n text = text.replace(\n /^[ \\t]*(?:#{1,6}[ \\t]+|>[ \\t]*|[-*+][ \\t]+|\\d+\\.[ \\t]+|-[ \\t]*\\[[ xX]\\][ \\t]+)/,\n \"\",\n );\n return text;\n };\n\n // Custom Dictionary substitutions run FIRST so the spoken form is locked in\n // before built-in abbreviation/number expansion can touch the same tokens.\n // Fill-in-the-blank underscores collapse to the spoken \"blank\" up front, before\n // the emphasis (`_italic_` / `__bold__`) rules could shred a bare \"___\" run.\n const source = normalizeSpeechBlanks(\n options?.pronunciations?.length\n ? applyPronunciations(markdown, options.pronunciations)\n : markdown,\n );\n\n // Code blocks FIRST, by THE one code-range rule (@ai-matrx/content-ir/source):\n // a diagram, a named language, or plain code — each spoken as one sentence.\n // Then tables (speakTables), BEFORE any other processing that would corrupt pipe/dash syntax.\n let result = speakTables(unwrapCodeSpans(replaceFences(stripLeadingMarkdown(stripReasoningTags(source)), ({ lang }) => {\n const language = lang.toLowerCase();\n if (language === \"mermaid\") return \"Please see the diagram provided.\";\n return SPOKEN_CODE_LANGUAGES.has(language)\n ? `Please see the ${language} code provided.`\n : \"Please see the code provided.\";\n })))\n // Blank any remaining pipe-led row that was not a whole table (fallback)\n .split(\"\\n\")\n .map((line) => (isPipeLedRow(line) && line.trimEnd().endsWith(\"|\") ? \"\" : line))\n .join(\"\\n\")\n // Replace numeric ranges (e.g. 0-2, 10-20) with \"X to Y\" before any dash stripping\n .replace(\n /\\b(\\d+)\\s*[-–—]\\s*(\\d+)\\b/g,\n (_match, a, b) => `${numberToWords(a)} to ${numberToWords(b)}`,\n )\n // Headings are read as-is, ending in a period so TTS pauses after them\n .replace(/^#{1,6}\\s+(.+)$/gm, (_m, t: string) =>\n /[.!?:]$/.test(t.trim()) ? t : `${t.trim()}.`,\n )\n // Remove comment markers but keep content\n .replace(/\\/\\/\\s*/g, \"\") // Remove // comment markers\n .replace(/--\\s*/g, \"\") // Remove -- comment markers\n .replace(/#\\s+/g, \"\") // Remove # comment markers (but not headers)\n .replace(/;\\s*/g, \"\") // Remove semicolon comment markers\n // Replace bold\n .replace(/(\\*\\*|__)(.*?)\\1/g, \"$2\")\n // Replace italic\n .replace(/(\\*|_)(.*?)\\1/g, \"$2\")\n // Replace strikethrough\n .replace(/~~(.*?)~~/g, \"$1\")\n // Replace highlighting\n .replace(/==(.*?)==/g, \"$1\")\n // Replace links with descriptive text\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, (_match, text, url) => {\n // Check if URL is an email\n if (url.startsWith(\"mailto:\")) {\n return `${text}. Email address provided.`;\n }\n // Check for common file extensions\n const fileExtensions = [\n \".pdf\",\n \".doc\",\n \".docx\",\n \".xls\",\n \".xlsx\",\n \".ppt\",\n \".pptx\",\n \".txt\",\n \".md\",\n \".json\",\n \".xml\",\n \".csv\",\n \".zip\",\n ];\n const hasFileExtension = fileExtensions.some((ext) =>\n url.toLowerCase().includes(ext),\n );\n if (hasFileExtension) {\n return `${text}. Document link provided.`;\n }\n return `${text}. Link provided.`;\n })\n // Replace images\n .replace(/!\\[([^\\]]*)\\]\\(([^)]+)\\)/g, ($0, alt) =>\n alt ? `${alt}. Image provided.` : \"Image provided.\",\n )\n // Replace blockquotes\n .replace(/^>\\s*(.+)$/gm, \"Quote: $1\")\n // Replace task lists\n .replace(/^-\\s*\\[x\\]\\s+(.+)$/gm, \"Completed task: $1\")\n .replace(/^-\\s*\\[\\s*\\]\\s+(.+)$/gm, \"Pending task: $1\")\n // Replace unordered lists\n .replace(/^([-*+])\\s+(.+)$/gm, \"$2\")\n // Replace ordered lists\n .replace(\n /^(\\d+)\\.\\s+(.+)$/gm,\n (_match, num, content) => `Number ${numberToWords(num)}: ${content}`,\n )\n // Replace footnotes and citations\n .replace(/\\[\\^(\\d+)\\]/g, \"Reference $1\")\n .replace(/^\\[\\d+\\]:\\s*(.+)$/gm, \"Reference: $1\")\n // Replace horizontal rules\n .replace(/^(\\*{3,}|-{3,}|_{3,})$/gm, \"\")\n // Replace mathematical expressions\n .replace(/\\$([^$]+)\\$/g, \"Mathematical expression: $1\")\n .replace(/\\$\\$([^$]+)\\$\\$/g, \"Mathematical formula: $1\")\n // Replace phone numbers\n .replace(\n /(\\+\\d{1,3}[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}/g,\n \"Phone number provided.\",\n )\n // Replace email addresses\n .replace(\n /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/g,\n \"Email address: $1\",\n )\n // Replace currency amounts\n .replace(/\\$(\\d+(?:,\\d{3})*(?:\\.\\d{2})?)/g, \"$1 dollars\")\n .replace(/€(\\d+(?:[.,]\\d{3})*(?:[.,]\\d{2})?)/g, \"$1 euros\")\n .replace(/£(\\d+(?:,\\d{3})*(?:\\.\\d{2})?)/g, \"$1 pounds\")\n .replace(/¥(\\d+(?:,\\d{3})*(?:\\.\\d{2})?)/g, \"$1 yen\")\n // Replace dates\n .replace(\n /(\\d{4})[-/](\\d{1,2})[-/](\\d{1,2})/g,\n (_match, year, month, day) => {\n const monthNames = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n ];\n const monthName = monthNames[parseInt(month) - 1] || month;\n return `${monthName} ${day}, ${year}`;\n },\n )\n // Replace times\n .replace(\n /(\\d{1,2}):(\\d{2})\\s*(AM|PM|am|pm)?/g,\n (_match, hour, minute, period) => {\n const hourNum = parseInt(hour);\n const periodText = period\n ? period.toUpperCase() === \"AM\"\n ? \"A.M.\"\n : \"P.M.\"\n : \"\";\n return `${hourNum} ${minute} ${periodText}`.trim();\n },\n )\n // Replace common emojis\n .replace(\n /[\\u{1F600}-\\u{1F64F}]|[\\u{1F300}-\\u{1F5FF}]|[\\u{1F680}-\\u{1F6FF}]|[\\u{1F1E0}-\\u{1F1FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu,\n (emoji) => {\n return emojiMap[emoji] || \"emoji\";\n },\n )\n // Initialisms become space-delimited letters; word acronyms stay natural.\n .replace(COMMON_ABBREVIATION_PATTERN, spokenFormForAbbreviation)\n // Replace measurement units (with word boundaries and optional numbers before)\n .replace(\n /(\\d+(?:\\.\\d+)?)\\s*(lbs|lb|oz|kg|gm|mg|km|cm|mm|ft|mi|mph|kph|kmh|ml|mL|gal|qt|tbsp|tsp|sqft|sqm|sec|min|hr|hrs|ms|psi|rpm|bpm)\\b/gi,\n (match, number, unit) => {\n const unitLower = unit.toLowerCase();\n const unitText =\n measurementUnits[unitLower] || measurementUnits[unit] || unit;\n return `${number} ${unitText}`;\n },\n )\n // Replace standalone measurement units (when preceded by space or number)\n .replace(\n /\\b(lbs|lb|oz|kg|gm|mg|km|cm|mm|mph|kph|kmh|ml|mL|gal|qt|tbsp|tsp|sqft|sqm|psi|rpm|bpm)(?=\\s|$|[.,;!?])/gi,\n (match) => {\n const unitLower = match.toLowerCase();\n return measurementUnits[unitLower] || measurementUnits[match] || match;\n },\n )\n // Replace special characters and symbols\n .replace(/©/g, \"copyright\")\n .replace(/®/g, \"registered trademark\")\n .replace(/™/g, \"trademark\")\n .replace(/°/g, \"degrees\")\n .replace(/±/g, \"plus or minus\")\n .replace(/≈/g, \"approximately\")\n .replace(/≠/g, \"not equal to\")\n .replace(/≤/g, \"less than or equal to\")\n .replace(/≥/g, \"greater than or equal to\")\n .replace(/=/g, \" equals \")\n .replace(/→/g, \"arrow\")\n .replace(/←/g, \"left arrow\")\n .replace(/↑/g, \"up arrow\")\n .replace(/↓/g, \"down arrow\")\n // Remove HTML tags, keep content\n .replace(/<\\/?[^>]+(>|$)/g, \"\")\n // Add period before newlines that follow text without terminal punctuation (ensures TTS pause)\n .replace(/([^\\s.!?,;:\\-])(\\n)/g, \"$1.\\n\")\n // Clean up multiple spaces\n .replace(/\\s+/g, \" \")\n // Trim whitespace\n .trim();\n\n return result;\n}\n","/**\n * Central Cartesia TTS configuration — the single source of truth for the model,\n * API version, system default voices, speed/volume baselines, and playback\n * buffering used by every in-app TTS surface (chat read-aloud, studio\n * read-aloud, voice playgrounds, the admin tester).\n *\n * Moved from matrx-frontend lib/cartesia/config.ts (P16 voices). The SDK-typed\n * `buildGenerationConfig` stays with the host's Cartesia client.\n */\n\n\n/** Current model + API version for all in-app TTS. */\nexport const TTS_MODEL_ID = \"sonic-3.5\";\nexport const CARTESIA_API_VERSION = \"2026-08-14\";\n\n/**\n * System default voices, used only when a user has not chosen their own.\n * - reading → Skylar (primary female; document / read-aloud)\n * - assistant → Daniel (primary male; assistant replies)\n */\nexport const READING_VOICE_ID = \"db6b0ed5-d5d3-463d-ae85-518a07d3c2b4\";\nexport const ASSISTANT_VOICE_ID = \"47c38ca4-5f35-497b-b1a3-415245fb35e1\";\n\n/**\n * The pre-2026 hardcoded default voice. Treated as \"unset\" by resolveVoiceId so\n * users who never explicitly chose a voice transition to the new defaults.\n */\nexport const LEGACY_DEFAULT_VOICE_ID = \"156fb8d2-335b-4950-9cb3-a2d33befec77\";\n\nexport type VoicePurpose = \"reading\" | \"assistant\";\n\n/** generation_config.speed range (1.0 = original). Our chosen baseline is 1.2. */\nexport const TTS_SPEED_MIN = 0.6;\nexport const TTS_SPEED_MAX = 1.5;\nexport const TTS_DEFAULT_SPEED = 1.2;\n/** generation_config.volume range (1.0 = original). */\nexport const TTS_DEFAULT_VOLUME = 1.0;\n\n/**\n * Client-side player buffer (seconds). Higher than the old 0.25s, which\n * caused stream underruns heard as choppy \"pauses\"; tune in one place.\n */\nexport const TTS_PLAYBACK_BUFFER_SEC = 0.7;\n\n/**\n * Lower buffer for token-by-token streaming (real-time LLM speech) where\n * latency matters more — still well above the old 0.1s that stuttered.\n */\nexport const TTS_STREAMING_BUFFER_SEC = 0.3;\n\n/** A user's explicit voice preference wins; otherwise the purpose default. */\nexport function resolveVoiceId(\n userVoiceId: string | null | undefined,\n purpose: VoicePurpose,\n): string {\n // MATRX-EXCEPTION: honest default for an explicitly-optional param — falls\n // through to the purpose default below, not a boundary write.\n const v = (userVoiceId ?? \"\").trim();\n if (v && v !== LEGACY_DEFAULT_VOICE_ID) return v;\n return purpose === \"reading\" ? READING_VOICE_ID : ASSISTANT_VOICE_ID;\n}\n\n/** Clamp a stored speed to the valid generation_config range, else the default. */\nexport function resolveSpeed(userSpeed: number | null | undefined): number {\n if (\n typeof userSpeed === \"number\" &&\n userSpeed >= TTS_SPEED_MIN &&\n userSpeed <= TTS_SPEED_MAX\n ) {\n return userSpeed;\n }\n return TTS_DEFAULT_SPEED;\n}\n","/**\n * THE single way to open a Cartesia TTS websocket from a browser (moved from matrx-frontend\n * lib/cartesia/connection.ts; token, failure report and toast arrive through configureSpeech).\n *\n * Cartesia v4 exposes a snake_case, event based websocket. The rest of the\n * app intentionally keeps one small, stable streaming contract, so SDK\n * upgrades stay contained here instead of leaking through every TTS hook.\n */\n\n// Types only at import: the Cartesia SDK (~20 kB gzip) loads when a socket is first opened, so every\n// importer of the speech entry (chat's composer via ./react) pays nothing until read-aloud runs.\nimport type { Cartesia } from \"@cartesia/cartesia-js/client\";\nimport type { GenerationConfig, VoiceSpecifier } from \"@cartesia/cartesia-js/resources/tts\";\nimport type { SupportedLanguage } from \"@cartesia/cartesia-js/resources/voices\";\nimport type { ProviderSessionFailure, ProviderSessionFailureVerdict } from \"@ai-matrx/agents/matrx\";\nimport { CARTESIA_API_VERSION } from \"../voices/tts-config\";\nimport { requirePort, speechPorts } from \"./configure\";\n\n/**\n * A provider session failure carrying the server's verdict: `message` is the sentence to show\n * (the provider's raw text when the report failed), and `retryable` says whether reconnecting helps.\n */\nexport class ProviderSessionError extends Error {\n override readonly name = \"ProviderSessionError\";\n readonly provider: ProviderSessionFailure[\"provider\"];\n readonly retryable: boolean;\n readonly errorType: string | null;\n readonly reported: boolean;\n\n constructor(\n failure: ProviderSessionFailure,\n verdict: ProviderSessionFailureVerdict | null,\n fallbackMessage: string,\n options?: { cause?: unknown },\n ) {\n super(verdict?.user_message || fallbackMessage, options);\n this.provider = failure.provider;\n // An unreported failure keeps the caller's existing retry behavior.\n this.retryable = verdict ? verdict.retryable : true;\n this.errorType = verdict?.error_type ?? failure.error_type ?? null;\n this.reported = verdict !== null;\n }\n}\n\n/** True when the provider rejected the token (refresh once and retry). */\nexport function isCartesiaAuthError(error: unknown): boolean {\n const message = error instanceof Error ? error.message : String(error ?? \"\");\n return /\\b(401|403)\\b|unauthorized|forbidden|invalid.*token|expired.*token|authentication/i.test(message);\n}\n\nasync function reportFailure(failure: ProviderSessionFailure): Promise<ProviderSessionFailureVerdict | null> {\n const report = speechPorts().reportProviderFailure;\n if (!report) return null;\n try {\n return await report(failure);\n } catch {\n return null;\n }\n}\n\nexport interface CartesiaTtsSocketOptions {\n container?: string;\n encoding?: string;\n sampleRate?: number;\n}\n\nexport type CartesiaTtsVoice = { mode: \"id\"; id: string };\n\nexport interface CartesiaTtsRequest {\n modelId: string;\n transcript: string;\n voice: CartesiaTtsVoice;\n language?: SupportedLanguage;\n contextId?: string;\n continue?: boolean;\n addTimestamps?: boolean;\n addPhonemeTimestamps?: boolean;\n maxBufferDelayMs?: number;\n generationConfig?: GenerationConfig;\n}\n\ntype NativeSocket = Awaited<ReturnType<Cartesia[\"tts\"][\"websocket\"]>>;\ntype CartesiaErrorEvent = {\n type: \"error\";\n message?: string;\n title?: string;\n status_code?: number;\n error_code?: string;\n context_id?: string;\n};\ntype MessageListener = (message: string) => void;\n\n/** The structural source consumed by SinkAwarePlayer. */\nexport class CartesiaAudioSource {\n readonly sampleRate: number;\n #chunks: Float32Array[] = [];\n #done = false;\n #waiter: (() => void) | null = null;\n\n constructor(sampleRate: number) {\n this.sampleRate = sampleRate;\n }\n\n durationToSampleCount(durationSecs: number): number {\n return Math.ceil(durationSecs * this.sampleRate);\n }\n\n push(bytes: Uint8Array): void {\n const sampleCount = Math.floor(bytes.byteLength / Float32Array.BYTES_PER_ELEMENT);\n const samples = new Float32Array(sampleCount);\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n for (let index = 0; index < sampleCount; index += 1) {\n samples[index] = view.getFloat32(index * Float32Array.BYTES_PER_ELEMENT, true);\n }\n this.#chunks.push(samples);\n this.#wake();\n }\n\n finish(): void {\n this.#done = true;\n this.#wake();\n }\n\n async read(destination: Float32Array): Promise<number> {\n let written = 0;\n while (written < destination.length) {\n while (this.#chunks.length === 0 && !this.#done) {\n await new Promise<void>((resolve) => {\n this.#waiter = resolve;\n });\n }\n if (this.#chunks.length === 0) break;\n const chunk = this.#chunks[0]!;\n const count = Math.min(chunk.length, destination.length - written);\n destination.set(chunk.subarray(0, count), written);\n written += count;\n if (count === chunk.length) {\n this.#chunks.shift();\n } else {\n this.#chunks[0] = chunk.subarray(count);\n }\n }\n return written;\n }\n\n #wake(): void {\n this.#waiter?.();\n this.#waiter = null;\n }\n}\n\nexport interface CartesiaTtsResponse {\n source: CartesiaAudioSource;\n on(event: \"message\", listener: MessageListener): void;\n}\n\nexport interface CartesiaConnectionCtx {\n on(event: \"close\", listener: () => void): void;\n}\n\nexport interface CartesiaTtsSocket {\n send(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse>;\n continue(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse>;\n disconnect(): void;\n}\n\nconst DEFAULTS: Required<CartesiaTtsSocketOptions> = {\n container: \"raw\",\n encoding: \"pcm_f32le\",\n sampleRate: 44100,\n};\n\ntype ResolvedSocketOptions = {\n container: \"raw\";\n encoding: \"pcm_f32le\";\n sampleRate: 8000 | 16000 | 22050 | 24000 | 44100 | 48000;\n};\n\nclass CartesiaV4Socket implements CartesiaTtsSocket {\n #native: NativeSocket;\n #options: ResolvedSocketOptions;\n #sources = new Map<string, CartesiaAudioSource>();\n #listeners = new Map<string, Set<MessageListener>>();\n\n /** Set when the provider refused this account and reconnecting cannot help. */\n #refused: ProviderSessionError | null = null;\n /** Sends accepted per context, for the usage idempotency key. */\n #sendCounts = new Map<string, number>();\n\n constructor(native: NativeSocket, options: ResolvedSocketOptions) {\n this.#native = native;\n this.#options = options;\n // The SDK also emits its typed `error` channel after the global event.\n // Bind it so a provider error does not become an unhandled rejection;\n // the event handler below finishes the affected source and notifies callers.\n native.on(\"error\", () => {});\n native.on(\"event\", (event) => {\n const contextId = \"context_id\" in event ? event.context_id : undefined;\n if (event.type === \"error\") {\n // A provider error is terminal for its context (or for every context\n // when it names none): end the audio now, and hand the error to its\n // listeners once the server has classified it.\n const listeners = contextId ? this.#listeners.get(contextId) : undefined;\n if (contextId) {\n this.#sources.get(contextId)?.finish();\n this.#sources.delete(contextId);\n this.#listeners.delete(contextId);\n } else {\n for (const source of this.#sources.values()) source.finish();\n this.#sources.clear();\n this.#listeners.clear();\n }\n void this.#reportProviderError(event, listeners);\n return;\n }\n if (!contextId) return;\n const source = this.#sources.get(contextId);\n if (event.type === \"chunk\" && source) {\n source.push(event.audio ?? decodeBase64(event.data));\n } else if (event.type === \"done\") {\n source?.finish();\n }\n const serialized = JSON.stringify(event);\n for (const listener of this.#listeners.get(contextId) ?? []) {\n listener(serialized);\n }\n if (event.type === \"done\") {\n this.#sources.delete(contextId);\n this.#listeners.delete(contextId);\n }\n });\n }\n\n /**\n * Browser-held session: the server never sees this provider error unless we\n * report it. Listeners get the event with `message` replaced by the server's\n * sentence, and the person sees it as a toast.\n */\n async #reportProviderError(\n event: CartesiaErrorEvent,\n listeners: Set<MessageListener> | undefined,\n ): Promise<void> {\n const failure: ProviderSessionFailure = {\n provider: \"cartesia\",\n status_code: event.status_code ?? null,\n error_type: event.error_code ?? event.title ?? null,\n message: event.message || event.title || \"Cartesia TTS error\",\n };\n const verdict = await reportFailure(failure);\n const error = new ProviderSessionError(failure, verdict, failure.message);\n if (!error.retryable) this.#refused = error;\n const delivered = JSON.stringify({\n ...event,\n message: error.message,\n retryable: error.retryable,\n });\n for (const listener of listeners ?? []) listener(delivered);\n // Most consumers only play the audio source, which just ends — without\n // this the refusal would be silent.\n speechPorts().notifyError?.(\"Speech stopped\", error.message);\n }\n\n async send(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse> {\n // A refusal reconnecting cannot fix (e.g. out of credit) stops this socket:\n // the SDK would otherwise reconnect transparently and ask again.\n if (this.#refused) throw this.#refused;\n const contextId = request.contextId ?? crypto.randomUUID();\n let source = this.#sources.get(contextId);\n if (!source) {\n source = new CartesiaAudioSource(this.#options.sampleRate);\n this.#sources.set(contextId, source);\n }\n\n const sent = this.#native.send(({\n context_id: contextId,\n model_id: request.modelId,\n transcript: request.transcript,\n voice: toNativeVoice(request.voice),\n output_format: {\n container: this.#options.container,\n encoding: this.#options.encoding,\n sample_rate: this.#options.sampleRate,\n },\n language: request.language,\n continue: request.continue,\n add_timestamps: request.addTimestamps,\n add_phoneme_timestamps: request.addPhonemeTimestamps,\n max_buffer_delay_ms: request.maxBufferDelayMs,\n generation_config: request.generationConfig,\n }) as Parameters<NativeSocket[\"send\"]>[0]);\n await sent;\n this.#reportUsage(contextId, request);\n\n return {\n source,\n on: (_event, listener) => {\n const listeners = this.#listeners.get(contextId) ?? new Set<MessageListener>();\n listeners.add(listener);\n this.#listeners.set(contextId, listeners);\n },\n };\n }\n\n /**\n * Cartesia bills the characters it is sent. Report each accepted send once: the context id plus a\n * per-context sequence is the idempotency key, so the host's retries can never double-bill.\n */\n #reportUsage(contextId: string, request: CartesiaTtsRequest): void {\n const characters = request.transcript?.length ?? 0;\n if (characters <= 0) return;\n const sequence = (this.#sendCounts.get(contextId) ?? 0) + 1;\n this.#sendCounts.set(contextId, sequence);\n try {\n speechPorts().reportProviderUsage?.({\n provider: \"cartesia\",\n model: request.modelId,\n usage_id: `${contextId}:${sequence}`,\n characters,\n });\n } catch {\n // Reporting never interrupts speech.\n }\n }\n\n continue(request: CartesiaTtsRequest): Promise<CartesiaTtsResponse> {\n return this.send(request);\n }\n\n disconnect(): void {\n for (const source of this.#sources.values()) source.finish();\n this.#sources.clear();\n this.#listeners.clear();\n this.#native.close();\n }\n}\n\nfunction decodeBase64(value: string): Uint8Array {\n const decoded = atob(value);\n const bytes = new Uint8Array(decoded.length);\n for (let index = 0; index < decoded.length; index += 1) {\n bytes[index] = decoded.charCodeAt(index);\n }\n return bytes;\n}\n\nfunction toNativeVoice(voice: CartesiaTtsVoice): VoiceSpecifier {\n return { id: voice.id };\n}\n\nasync function buildConnectedSocket(\n token: string,\n options: ResolvedSocketOptions,\n): Promise<{ ws: CartesiaV4Socket; ctx: CartesiaConnectionCtx }> {\n const { Cartesia: CartesiaClient } = await import(\"@cartesia/cartesia-js/client\");\n const client = new CartesiaClient({ token });\n const native = await client.tts.websocket({\n headers: { \"Cartesia-Version\": CARTESIA_API_VERSION },\n });\n await native.connect();\n const closeListeners = new Set<() => void>();\n native.socket.addEventListener(\"close\", () => {\n for (const listener of closeListeners) listener();\n });\n return {\n ws: new CartesiaV4Socket(native, options),\n ctx: {\n on: (_event, listener) => {\n closeListeners.add(listener);\n },\n },\n };\n}\n\n/** Open a connected socket, refreshing a rejected broker token once. */\nexport async function connectCartesiaTts(\n options?: CartesiaTtsSocketOptions,\n): Promise<{ ws: CartesiaTtsSocket; ctx: CartesiaConnectionCtx }> {\n const requested = { ...DEFAULTS, ...options };\n if (requested.container !== \"raw\") {\n throw new Error(\"Cartesia WebSocket playback requires the raw audio container.\");\n }\n if (requested.encoding !== \"pcm_f32le\") {\n throw new Error(\"Cartesia browser playback requires pcm_f32le audio.\");\n }\n const sampleRates: readonly number[] = [8000, 16000, 22050, 24000, 44100, 48000];\n if (!sampleRates.includes(requested.sampleRate)) {\n throw new Error(`Unsupported Cartesia sample rate: ${requested.sampleRate}`);\n }\n const resolved = requested as ResolvedSocketOptions;\n const getToken = requirePort(\"getCartesiaToken\");\n const token = await getToken();\n try {\n try {\n return await buildConnectedSocket(token, resolved);\n } catch (error) {\n if (!isCartesiaAuthError(error)) throw error;\n speechPorts().invalidateCartesiaToken?.(token);\n const refreshed = await getToken({ forceRefresh: true }).catch(\n (mintError: unknown) => {\n throw new BrokerMintFailure(mintError);\n },\n );\n return await buildConnectedSocket(refreshed, resolved);\n }\n } catch (error) {\n // The broker mint already reports its own failures server-side; only the\n // browser → Cartesia handshake is reported here.\n if (error instanceof BrokerMintFailure) throw error.cause;\n throw await reportCartesiaConnectFailure(error);\n }\n}\n\n/** Marks a mint failure inside the retry so it is not reported as Cartesia's. */\nclass BrokerMintFailure extends Error {\n constructor(override readonly cause: unknown) {\n super(\"broker mint failed\");\n }\n}\n\nasync function reportCartesiaConnectFailure(\n error: unknown,\n): Promise<ProviderSessionError> {\n const raw =\n error instanceof Error\n ? error.message\n : typeof error === \"object\" && error && \"message\" in error\n ? String((error as { message: unknown }).message)\n : \"\";\n const status = /\\b([45]\\d\\d)\\b/.exec(raw)?.[1];\n const failure: ProviderSessionFailure = {\n provider: \"cartesia\",\n status_code: status ? Number(status) : null,\n message: raw || \"Cartesia WebSocket connection failed\",\n };\n const verdict = await reportFailure(failure);\n return new ProviderSessionError(\n failure,\n verdict,\n raw || \"Could not connect to the speech service.\",\n { cause: error },\n );\n}\n","// features/audio/audioOutputSink.ts\n//\n// Universal audio OUTPUT (speaker) routing — the single place the app decides\n// which physical output device every sound plays through.\n//\n// WHY THIS EXISTS\n// ---------------\n// Two unrelated playback paths exist: real `HTMLMediaElement`s (`<audio>` /\n// `<video>` via `InlineMediaRef`) and Web Audio playback via our\n// `SinkAwarePlayer` (features/audio/sinkAwarePlayer.ts — the fork of the\n// Cartesia SDK WebPlayer). The browser exposes TWO sink APIs for these:\n//\n// • `HTMLMediaElement.setSinkId(deviceId)` — Chrome/Firefox; routes one media\n// element. Used directly by `InlineMediaRef` via `applySinkToMediaElement`.\n// • `AudioContext.setSinkId(deviceId)` — Chromium-only; routes a Web Audio\n// graph. `SinkAwarePlayer` owns its per-utterance contexts and calls this\n// itself: it reads this store at context creation and subscribes for\n// mid-utterance device changes. (The old global `AudioContext` constructor\n// monkeypatch — `installAudioContextSinkRouting` + the `NO_SINK_ROUTING`\n// opt-out sentinel — is DELETED; nothing patches globals anymore.)\n//\n// SAFARI has NEITHER API. Everything here feature-detects and no-ops gracefully;\n// on Safari the user picks the output device in macOS/iOS settings.\n//\n// One store, one subscribe channel, two appliers. The audio-devices manager is\n// the only writer (`setPreferredOutputDeviceId`); InlineMediaRef +\n// SinkAwarePlayer are the readers.\n\ntype SinkListener = (deviceId: string) => void;\n\ninterface SinkState {\n /** Current preferred output deviceId. \"\" = system default. */\n deviceId: string;\n listeners: Set<SinkListener>;\n}\n\nconst state: SinkState = {\n deviceId: \"\",\n listeners: new Set(),\n};\n\n/** True when `HTMLMediaElement.setSinkId` exists (Chrome/Firefox, not Safari). */\nexport function mediaElementSinkSupported(): boolean {\n return (\n typeof HTMLMediaElement !== \"undefined\" &&\n \"setSinkId\" in HTMLMediaElement.prototype\n );\n}\n\n/** True when `AudioContext.setSinkId` exists (Chromium only). */\nexport function audioContextSinkSupported(): boolean {\n if (typeof window === \"undefined\") return false;\n const Ctor =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext?: typeof AudioContext })\n .webkitAudioContext;\n return !!Ctor && \"setSinkId\" in Ctor.prototype;\n}\n\n/**\n * True when the browser can route output at all (either API). When false, the\n * speaker picker is disabled and the user is told to choose output in OS\n * settings.\n */\nexport function outputSelectionSupported(): boolean {\n return mediaElementSinkSupported() || audioContextSinkSupported();\n}\n\n/** Read the current preferred output deviceId (\"\" = system default). */\nexport function getPreferredOutputDeviceId(): string {\n return state.deviceId;\n}\n\n/**\n * Set the preferred output device. Notifies subscribers (InlineMediaRef\n * re-applies to its live media elements; SinkAwarePlayer re-routes any live\n * playback context mid-utterance). Idempotent — a no-op when unchanged.\n */\nexport function setPreferredOutputDeviceId(deviceId: string): void {\n if (state.deviceId === deviceId) return;\n state.deviceId = deviceId;\n for (const l of state.listeners) {\n try {\n l(deviceId);\n } catch {\n // never let a listener break the store\n }\n }\n}\n\n/** Subscribe to preferred-output changes. Returns an unsubscribe fn. */\nexport function subscribeOutputDevice(listener: SinkListener): () => void {\n state.listeners.add(listener);\n return () => {\n state.listeners.delete(listener);\n };\n}\n\n/**\n * Apply the current preferred sink to one `HTMLMediaElement`. Feature-detected;\n * on Safari (no `setSinkId`) or \"\" (system default) it's a clean no-op. A real\n * failure (e.g. the device vanished) is reported loudly, never swallowed — the\n * element keeps playing on the default device.\n */\nexport async function applySinkToMediaElement(\n el: HTMLMediaElement | null | undefined,\n): Promise<void> {\n if (!el) return;\n if (!mediaElementSinkSupported()) return;\n const target = state.deviceId;\n // Empty string is a valid argument to setSinkId meaning \"system default\".\n const elWithSink = el as HTMLMediaElement & {\n sinkId?: string;\n setSinkId?: (id: string) => Promise<void>;\n };\n if (elWithSink.sinkId === target) return; // already routed\n try {\n await elWithSink.setSinkId?.(target);\n } catch (err) {\n\n console.error(\n \"[audioOutputSink] setSinkId on media element failed — falling back to \" +\n \"the system default device. Requested device may be unavailable.\",\n { deviceId: target, error: err },\n );\n }\n}\n","// features/audio/sinkAwarePlayer.ts\n//\n// SinkAwarePlayer — our fork of the Cartesia SDK `WebPlayer` (~140 lines\n// upstream), kept drop-in compatible with its playback contract\n// (`play(source)` / `pause()` / `resume()` / `toggle()` / `stop()`), but\n// OWNING its AudioContext so output-device (speaker) routing is explicit\n// instead of relying on a global `AudioContext` constructor monkeypatch.\n//\n// WHY THE FORK EXISTS (FOUND_DEFECTS D13)\n// ---------------------------------------\n// The SDK's WebPlayer builds a hard-private AudioContext per `play()` call\n// with no accessible handle, so the only way to route it to the user's chosen\n// speaker was patching the global `AudioContext` constructor\n// (`installAudioContextSinkRouting`, now deleted). That patch was invisible\n// action-at-a-distance: every context in the app inherited the sink unless it\n// opted out via a sentinel, and a device change could never reach a live\n// utterance. This fork:\n//\n// 1. Applies the preferred output device (from the same store the\n// `userPreferences.mediaDevices` module feeds via\n// `providers/AudioDeviceProviderImpl` → `applyOutputDevice`) to its own\n// context at creation, and\n// 2. Subscribes to `subscribeOutputDevice` while a context is live, so a\n// device change re-routes MID-UTTERANCE (`AudioContext.setSinkId`).\n//\n// BROWSER SUPPORT: `AudioContext.setSinkId` is Chromium-only. On Firefox and\n// Safari the sink calls feature-detect and no-op — playback continues on the\n// SYSTEM DEFAULT device (the user picks output in OS settings), never a crash.\n// A real setSinkId failure (device unplugged mid-play) is reported loudly and\n// playback continues on the default device.\n//\n// CONTRACT PARITY WITH THE SDK WebPlayer\n// --------------------------------------\n// - `play(source)` builds a fresh AudioContext per utterance at the source's\n// sample rate, reads `bufferDuration`-sized chunks, schedules them\n// back-to-back, and resolves when playback finishes.\n// - `pause()` / `resume()` / `toggle()` suspend/resume the context and throw\n// \"AudioContext not initialized.\" before the first play — callers guard\n// with hasPlayed refs / try-catch exactly as they did against the SDK.\n// - `stop()` closes the context. Unlike the SDK it is close-idempotent: a\n// second stop on an already-closed context is a silent no-op (the SDK threw\n// \"Cannot close a closed AudioContext\", which every caller had to catch).\n\nimport {\n getPreferredOutputDeviceId,\n subscribeOutputDevice,\n} from \"./output-sink\";\nimport { getUnlockedAudioContext } from \"./unlock\";\n\n/**\n * The slice of the Cartesia `Source` contract the player actually consumes.\n * Structural — `@cartesia/cartesia-js`'s `Source` satisfies it — so the\n * player has no hard SDK type dependency and is unit-testable with a stub.\n */\nexport interface PlayableSource {\n readonly sampleRate: number;\n /** Read up to `dst.length` samples; resolves with the count actually read.\n * A short read (< dst.length) signals the end of the source. */\n read(dst: Float32Array): Promise<number>;\n /** Number of samples covering `durationSecs` at this source's sample rate. */\n durationToSampleCount(durationSecs: number): number;\n}\n\n/** Minimal AudioContext surface the player uses — injectable for tests. */\nexport interface SinkAwareAudioContext {\n readonly currentTime: number;\n readonly state: AudioContextState;\n readonly destination: AudioNode;\n createBufferSource(): AudioBufferSourceNode;\n createBuffer(\n channels: number,\n length: number,\n sampleRate: number,\n ): AudioBuffer;\n suspend(): Promise<void>;\n resume(): Promise<void>;\n close(): Promise<void>;\n /** Chromium-only; absent on Firefox/Safari. */\n setSinkId?: (deviceId: string) => Promise<void>;\n}\n\nexport interface SinkAwarePlayerOptions {\n /** Seconds of audio to buffer per scheduling chunk (same as the SDK). */\n bufferDuration: number;\n /**\n * Test seam: build the per-utterance context. Defaults to\n * `new AudioContext({ sampleRate })`.\n */\n createContext?: (sampleRate: number) => SinkAwareAudioContext;\n}\n\nfunction defaultCreateContext(sampleRate: number): SinkAwareAudioContext {\n return new AudioContext({ sampleRate });\n}\n\nexport class SinkAwarePlayer {\n #context: SinkAwareAudioContext | null = null;\n #startNextPlaybackAt = 0;\n #bufferDuration: number;\n #createContext: (sampleRate: number) => SinkAwareAudioContext;\n #unsubscribeSink: (() => void) | null = null;\n /**\n * True when `#context` is the app's SHARED gesture-unlocked context\n * (features/audio/unlock.ts) rather than a private per-utterance one.\n * Shared mode exists for iOS/WebKit, where a context created outside a\n * user gesture starts `suspended` forever — our audio starts from\n * websocket callbacks, so per-utterance contexts were silent on iPhone.\n * In shared mode the utterance plays through a per-utterance GainNode and\n * `stop()` tears that down instead of closing the context.\n */\n #sharedMode = false;\n #utteranceGain: GainNode | null = null;\n #liveSources = new Set<AudioBufferSourceNode>();\n #stopped = false;\n\n constructor({ bufferDuration, createContext }: SinkAwarePlayerOptions) {\n this.#bufferDuration = bufferDuration;\n this.#createContext = createContext ?? defaultCreateContext;\n }\n\n /**\n * Play audio from a source. Resolves when the audio has finished playing.\n *\n * Prefers the app's shared gesture-unlocked context (required for sound on\n * iOS/WebKit); otherwise builds a fresh AudioContext per call (matching the\n * SDK). Either way the output is routed to the user's preferred device and\n * re-routed live on device change. Web Audio resamples per-buffer, so the\n * shared context's hardware rate plays 44.1k sources correctly.\n */\n async play(source: PlayableSource): Promise<void> {\n this.#startNextPlaybackAt = 0;\n this.#stopped = false;\n this.#detachSinkSubscription();\n // Custom createContext (tests) keeps full legacy behavior.\n const shared = this.#createContext === defaultCreateContext\n ? getUnlockedAudioContext()\n : null;\n const context: SinkAwareAudioContext =\n shared ?? this.#createContext(source.sampleRate);\n this.#sharedMode = shared !== null;\n this.#context = context;\n if (this.#sharedMode) {\n const ctx = shared as AudioContext;\n this.#utteranceGain = ctx.createGain();\n this.#utteranceGain.connect(ctx.destination);\n } else {\n this.#utteranceGain = null;\n }\n this.#attachSinkRouting(context);\n\n // A non-running context plays NOTHING while accepting every schedule\n // call — the exact silent-on-iPhone failure. iOS also parks contexts in a\n // non-standard \"interrupted\" state after a phone call / Siri. Resume\n // (allowed once the page was gesture-unlocked); if it won't run, fail\n // LOUDLY so the queue and the Listen panel show an error with a\n // tap-to-retry instead of silence.\n if ((context.state as string) !== \"running\") {\n try {\n await context.resume();\n } catch {\n /* judged below by the resulting state */\n }\n if ((context.state as string) !== \"running\") {\n throw new Error(\n \"The browser blocked audio output — tap the play button to start sound.\",\n );\n }\n }\n\n try {\n const buffer = new Float32Array(\n source.durationToSampleCount(this.#bufferDuration),\n );\n const plays: Promise<void>[] = [];\n while (true) {\n const read = await source.read(buffer);\n // A short read means the source is exhausted — only play what was\n // actually filled, otherwise the tail of the previous chunk repeats.\n const playableAudio = buffer.subarray(0, read);\n plays.push(this.#playBuffer(playableAudio, source.sampleRate));\n if (read < buffer.length) break;\n }\n await Promise.all(plays);\n } finally {\n // The utterance is over (or failed) — stop tracking device changes for\n // this context. The context itself stays open until stop(), matching\n // the SDK (pause/resume after natural completion remain valid).\n this.#detachSinkSubscription();\n }\n }\n\n /** Suspend playback. Throws before the first play (contract parity). */\n async pause(): Promise<void> {\n const context = this.#requireContext();\n if (this.#sharedMode && this.#stopped) return;\n await context.suspend();\n }\n\n /** Resume suspended playback. Throws before the first play. */\n async resume(): Promise<void> {\n const context = this.#requireContext();\n if (this.#sharedMode && this.#stopped) return;\n await context.resume();\n }\n\n /** Pause when running, resume otherwise. Throws before the first play. */\n async toggle(): Promise<void> {\n if (this.#requireContext().state === \"running\") {\n await this.pause();\n } else {\n await this.resume();\n }\n }\n\n /**\n * Stop playback. Throws before the first play (contract parity); a repeat\n * stop is a silent no-op.\n *\n * Owned-context mode closes the context (the SDK contract). Shared mode\n * NEVER closes the app-wide unlocked context — it stops this utterance's\n * scheduled sources and tears down its GainNode, and resumes the context\n * if this utterance had paused it (a suspended shared context would mute\n * the NEXT utterance too).\n */\n async stop(): Promise<void> {\n const context = this.#requireContext();\n this.#detachSinkSubscription();\n if (this.#sharedMode) {\n if (this.#stopped) return;\n this.#stopped = true;\n for (const node of this.#liveSources) {\n try {\n node.stop();\n } catch {\n /* already ended */\n }\n }\n this.#liveSources.clear();\n try {\n this.#utteranceGain?.disconnect();\n } catch {\n /* already disconnected */\n }\n this.#utteranceGain = null;\n if (context.state === \"suspended\") {\n await context.resume().catch(() => {});\n }\n return;\n }\n if (context.state === \"closed\") return;\n await context.close();\n }\n\n #requireContext(): SinkAwareAudioContext {\n if (!this.#context) {\n throw new Error(\"AudioContext not initialized.\");\n }\n return this.#context;\n }\n\n /**\n * Route `context` to the current preferred output device and keep it\n * routed while it lives. Feature-detected: on Firefox/Safari (no\n * `AudioContext.setSinkId`) this is a clean no-op and audio plays on the\n * system default device.\n */\n #attachSinkRouting(context: SinkAwareAudioContext): void {\n if (typeof context.setSinkId !== \"function\") return;\n\n const applySink = (deviceId: string) => {\n if (this.#context !== context || context.state === \"closed\") return;\n // \"\" = system default. Unlike HTMLMediaElement.setSinkId, passing \"\"\n // to AudioContext.setSinkId is valid and means the default device.\n context.setSinkId?.(deviceId).catch((err: unknown) => {\n console.error(\n \"[SinkAwarePlayer] setSinkId failed — playback continues on the \" +\n \"system default device. The requested device may be unavailable.\",\n { deviceId, error: err },\n );\n });\n };\n\n // Apply the persisted choice now (skip \"\" — a fresh context already IS\n // the system default)…\n const initial = getPreferredOutputDeviceId();\n if (initial) applySink(initial);\n // …and follow changes mid-utterance.\n this.#unsubscribeSink = subscribeOutputDevice(applySink);\n }\n\n #detachSinkSubscription(): void {\n this.#unsubscribeSink?.();\n this.#unsubscribeSink = null;\n }\n\n async #playBuffer(buf: Float32Array, sampleRate: number): Promise<void> {\n const context = this.#requireContext();\n if (buf.length === 0) return;\n if (this.#sharedMode && this.#stopped) return;\n const startAt = this.#startNextPlaybackAt;\n const duration = buf.length / sampleRate;\n this.#startNextPlaybackAt =\n duration + Math.max(context.currentTime, this.#startNextPlaybackAt);\n\n const sourceNode = context.createBufferSource();\n // The buffer carries the SOURCE sample rate; when it differs from the\n // context's hardware rate (shared mode), Web Audio resamples on playback.\n const audioBuffer = context.createBuffer(1, buf.length, sampleRate);\n audioBuffer.getChannelData(0).set(buf);\n sourceNode.buffer = audioBuffer;\n sourceNode.connect(this.#utteranceGain ?? context.destination);\n sourceNode.start(startAt);\n if (this.#sharedMode) this.#liveSources.add(sourceNode);\n await new Promise<void>((resolve) => {\n sourceNode.onended = () => {\n this.#liveSources.delete(sourceNode);\n resolve();\n };\n });\n }\n}\n","/**\n * Cartesia playback adapter.\n *\n * Imperative twin of `useCartesiaSpeaker.speak()`: token → WebSocket → send →\n * SinkAwarePlayer. Lives outside React so the singleton `playbackQueue` can\n * drive it. Output device routing is owned by the player itself — it applies\n * the preferred speaker at context creation and re-routes mid-utterance on\n * device change (see features/audio/sinkAwarePlayer.ts).\n *\n * Note: Cartesia \"speed\" is a synthesis-time parameter, so live rate changes are\n * not supported (no `setRate`). The queue's global rate is captured into the\n * synthesis `speed` at enqueue time by the consumer instead.\n */\n\nimport { parseMarkdownToText } from \"../voices/speech-text\";\nimport { resolveVoiceId, TTS_MODEL_ID, TTS_PLAYBACK_BUFFER_SEC, resolveSpeed, TTS_DEFAULT_VOLUME } from \"../voices/tts-config\";\nimport type { SpeechPronunciation } from \"../voices/speech-text\";\nimport { connectCartesiaTts } from \"./cartesia-socket\";\nimport { requirePort, speechPorts } from \"./configure\";\nimport { SinkAwarePlayer } from \"./sink-aware-player\";\nimport type { ActivePlayback, PlaybackAdapter, PlaybackAdapterCallbacks, PlaybackItem } from \"./types\";\n\n/** Cartesia generation config from the person's speed / emotion. */\nexport function buildGenerationConfig(opts?: {\n speed?: number | null;\n volume?: number | null;\n emotion?: string | null;\n}): { speed: number; volume: number; emotion?: string } {\n const cfg: { speed: number; volume: number; emotion?: string } = {\n speed: resolveSpeed(opts?.speed),\n volume: opts?.volume ?? TTS_DEFAULT_VOLUME,\n };\n if (opts?.emotion) cfg.emotion = opts.emotion.toLowerCase();\n return cfg;\n}\n\nasync function resolveText(item: PlaybackItem): Promise<string> {\n let pronunciations: SpeechPronunciation[] = [];\n // Custom pronunciations (the web's dictionary) are best-effort: a failure speaks without them.\n try {\n pronunciations = (await speechPorts().resolvePronunciations?.(item)) ?? [];\n } catch {\n pronunciations = [];\n }\n const processMarkdown = item.processMarkdown ?? true;\n return processMarkdown\n ? parseMarkdownToText(item.text, pronunciations.length ? { pronunciations } : undefined)\n : item.text;\n}\n\nexport const cartesiaAdapter: PlaybackAdapter = {\n provider: \"cartesia\",\n\n async start(\n item: PlaybackItem,\n cb: PlaybackAdapterCallbacks,\n ): Promise<ActivePlayback> {\n cb.onLoading();\n\n // START-time resolution: the item carries only the caller's EXPLICIT\n // overrides; everything else comes from the settings ladder (voice:\n // organization → user → device) and the tiered listening config (speed,\n // language) as they stand NOW — so queued items and history replays\n // honor settings changed after they were enqueued. Awaited: a cold\n // cache must never pick the default voice for a paid utterance.\n const overrides = item.cartesia ?? {};\n const settings = await requirePort(\"resolveVoiceSettings\")();\n const voice = {\n voiceId:\n overrides.voiceId ??\n resolveVoiceId(settings.voice, overrides.purpose ?? \"assistant\"),\n language: overrides.language ?? settings.language,\n speed: overrides.speed ?? settings.speed,\n emotion: settings.emotion,\n };\n\n const processed = (await resolveText(item)).trim();\n if (!processed) {\n throw new Error(\"Nothing to speak\");\n }\n\n // Auth + WebSocket via the single Cartesia connection chokepoint.\n const { ws } = await connectCartesiaTts();\n\n let stopped = false;\n const player = new SinkAwarePlayer({\n bufferDuration: TTS_PLAYBACK_BUFFER_SEC,\n });\n\n const cleanup = () => {\n try {\n ws.disconnect();\n } catch {\n /* already closed */\n }\n };\n\n const response = await ws.send({\n modelId: TTS_MODEL_ID,\n voice: { mode: \"id\" as const, id: voice.voiceId },\n language: (voice.language || \"en\") as never,\n transcript: processed,\n generationConfig: buildGenerationConfig({ speed: voice.speed, emotion: voice.emotion ?? null }) as never,\n });\n\n if (stopped) {\n cleanup();\n throw new Error(\"Playback cancelled before it started\");\n }\n\n cb.onPlaying();\n\n // play() resolves when playback FINISHES — do NOT await it here.\n player\n .play(response.source)\n .then(() => {\n if (stopped) return;\n cleanup();\n cb.onEnded();\n })\n .catch((err) => {\n if (stopped) return;\n cleanup();\n cb.onError(err instanceof Error ? err.message : \"Playback failed\");\n });\n\n return {\n pause: () => player.pause().catch(() => {}),\n resume: () => player.resume().catch(() => {}),\n stop: async () => {\n stopped = true;\n try {\n await player.stop();\n } catch {\n /* noop */\n }\n cleanup();\n },\n };\n },\n};\n","/**\n * @ai-matrx/media/speech — THE one speech engine (framework-free): `speak()`, the app-wide\n * playback queue + lock, the Cartesia streaming lane, the sink-aware player and the iOS/WebKit\n * output unlock. A host wires its token, voice settings and failure report once with\n * `configureSpeech`. React: `useSpeech` / `usePlaybackSnapshot` in `@ai-matrx/media/react`.\n */\nexport { configureSpeech, isSpeechConfigured, resetSpeechPortsForTests, type SpeechPorts, type SpeechVoiceSettings } from \"./speech/configure\";\nexport { speak, resolveSpeakEngine, type SpeakRequest, type SpeakResult } from \"./speech/speak\";\nexport {\n enqueuePlayback,\n pausePlayback,\n resumePlayback,\n skipPlayback,\n playPlaybackItem,\n removePlaybackItem,\n clearPlayback,\n setPlaybackRate,\n getPlaybackSnapshot,\n subscribePlayback,\n resetPlaybackForTests,\n type EnqueueResult,\n} from \"./speech/playback-queue\";\nexport * from \"./speech/playback-lock\";\nexport * from \"./speech/types\";\nexport * from \"./speech/unlock\";\nexport * from \"./speech/output-sink\";\nexport * from \"./speech/sink-aware-player\";\nexport {\n connectCartesiaTts,\n CartesiaAudioSource,\n ProviderSessionError,\n isCartesiaAuthError,\n type CartesiaTtsSocket,\n type CartesiaTtsSocketOptions,\n type CartesiaTtsRequest,\n type CartesiaTtsResponse,\n type CartesiaTtsVoice,\n type CartesiaConnectionCtx,\n} from \"./speech/cartesia-socket\";\nexport { cartesiaAdapter, buildGenerationConfig } from \"./speech/cartesia-adapter\";\n","/**\n * speak() — THE entry point for turning text into audio, in every app.\n *\n * One call, any engine. The caller says WHAT to say (and optionally which engine); the queue owns\n * one-at-a-time playback, the app-wide playback lock and lazy-loading of the engine. Cartesia's\n * voice / language / speed resolve at START time through the host's `resolveVoiceSettings` port,\n * so queued items and replays honor the settings current when audio plays. Framework-free;\n * React surfaces use `useSpeech()` (`@ai-matrx/media/react`) on top of the same call.\n */\n\nimport type { VoicePurpose } from \"../voices/tts-config\";\nimport { speechPorts } from \"./configure\";\nimport { enqueuePlayback } from \"./playback-queue\";\nimport type { PlaybackProvider, PlaybackRequest } from \"./types\";\n\nexport interface SpeakRequest {\n text: string;\n /** Engine to synthesize with. Omit for the host's default (Cartesia unless configured). */\n engine?: PlaybackProvider | undefined;\n /** Which voice slot to resolve for engines with per-purpose voices (Cartesia). */\n purpose?: VoicePurpose | undefined;\n /** Strip markdown before speaking (default true). */\n processMarkdown?: boolean | undefined;\n /** Short human label for a queue / Media panel row. */\n label?: string | undefined;\n /** Opt this utterance into Custom Dictionary pronunciation (host-resolved). */\n dictionarySurfaceKey?: string | undefined;\n /** Explicit voice id — overrides the saved voice for this utterance. */\n voice?: string | undefined;\n /** Explicit language — overrides the saved language. */\n language?: string | undefined;\n /** Explicit speed — overrides the saved speed. */\n speed?: number | undefined;\n /** Hear a catalog model's sample of one voice instead of speaking `text` (catalog engine). */\n sample?: { model: string; voice: string; offeringId?: string | undefined } | undefined;\n}\n\nexport interface SpeakResult {\n /** Queue item id — feed it to the queue's pause/resume/remove verbs. */\n id: string;\n /** The engine that actually ran (after defaulting). */\n engine: PlaybackProvider;\n}\n\n/** The engine for a request: explicit wins, otherwise the host default, otherwise Cartesia. */\nexport function resolveSpeakEngine(requested?: PlaybackProvider): PlaybackProvider {\n return requested ?? speechPorts().defaultEngine ?? \"cartesia\";\n}\n\n/** Speak text through the single playback queue. */\nexport function speak(request: SpeakRequest): SpeakResult {\n const complete = (r: PlaybackRequest): PlaybackRequest => speechPorts().completeRequest?.(r) ?? r;\n if (request.sample) {\n const { id } = enqueuePlayback(\n complete({\n text: request.text,\n processMarkdown: false,\n label: request.label,\n provider: \"catalog\",\n catalog: { sample: request.sample },\n }),\n );\n return { id, engine: \"catalog\" };\n }\n const engine = resolveSpeakEngine(request.engine);\n const common = {\n text: request.text,\n processMarkdown: request.processMarkdown,\n label: request.label,\n dictionarySurfaceKey: request.dictionarySurfaceKey,\n };\n if (engine === \"cartesia\") {\n // Only the caller's EXPLICIT overrides ride the item; the adapter resolves the rest at START.\n const { id } = enqueuePlayback(\n complete({\n ...common,\n provider: \"cartesia\",\n cartesia: {\n voiceId: request.voice,\n language: request.language,\n speed: request.speed,\n purpose: request.purpose ?? \"assistant\",\n },\n }),\n );\n return { id, engine };\n }\n const { id } = enqueuePlayback(complete({ ...common, provider: engine, catalog: { voice: request.voice } }));\n return { id, engine };\n}\n","/**\n * playbackQueue — the single, app-wide audio playback queue.\n *\n * Framework-free singleton (the playback twin of `captureLock`). Every \"speak\n * this\" request becomes an item; audio plays ONE at a time. New requests while\n * something is playing are appended (queued, never overlapped). Finished items\n * stay in history (status `done`) until cleared so the UI can offer replay.\n *\n * Providers plug in via `PlaybackAdapter`s, so the queue never knows about\n * Cartesia WebSockets or Groq blobs.\n *\n * Moved from matrx-frontend (features/audio/playback/playbackQueue.ts) into @ai-matrx/media so\n * every app plays through ONE queue. A host may mirror snapshots (the web mirrors into Redux for\n * its Media panel); React reads it through `useSpeech` / `usePlaybackSnapshot`.\n */\n\nimport { speechPorts } from \"./configure\";\nimport { primeAudioOutput } from \"./unlock\";\nimport { claimPlayback, releasePlayback } from \"./playback-lock\";\nimport type {\n ActivePlayback,\n PlaybackAdapter,\n PlaybackItem,\n PlaybackProvider,\n PlaybackRequest,\n PlaybackSnapshot,\n} from \"./types\";\n\n/** This queue's identity in the app-wide playback lock. */\nconst QUEUE_HOLDER_ID = \"playback-queue\";\n\n// Adapters are loaded lazily on first use so the heavy provider SDKs (Cartesia\n// WebSocket client, etc.) stay OUT of the app shell — this module is imported by\n// the always-mounted AudioPlaybackHost. Nothing TTS loads until the first\n// `speak`. Cached after first load (engage once, stay engaged).\nconst adapterCache = new Map<PlaybackProvider, PlaybackAdapter>();\n\nasync function getAdapter(\n provider: PlaybackProvider,\n): Promise<PlaybackAdapter> {\n const cached = adapterCache.get(provider);\n if (cached) return cached;\n let adapter: PlaybackAdapter;\n const registered = speechPorts().adapters?.[provider];\n if (registered) {\n adapter = await registered();\n } else if (provider === \"cartesia\") {\n adapter = (await import(\"./cartesia-adapter\")).cartesiaAdapter;\n } else {\n throw new Error(`No speech engine \"${provider}\" is set up in this app.`);\n }\n adapterCache.set(provider, adapter);\n return adapter;\n}\n\ntype Listener = (snapshot: PlaybackSnapshot) => void;\n\nlet items: PlaybackItem[] = [];\nlet currentId: string | null = null;\nlet rate = 1;\nlet active: ActivePlayback | null = null;\nlet runToken = 0;\nconst listeners = new Set<Listener>();\n\nlet seq = 0;\nfunction nextId(): string {\n seq += 1;\n return `pb_${Date.now().toString(36)}_${seq}`;\n}\n\nfunction snapshot(): PlaybackSnapshot {\n return { items: items.map((i) => ({ ...i })), currentId, rate };\n}\n\nfunction notify(): void {\n const snap = snapshot();\n listeners.forEach((l) => {\n try {\n l(snap);\n } catch (err) {\n console.error(\"[playbackQueue] listener error\", err);\n }\n });\n}\n\nfunction patch(id: string, changes: Partial<PlaybackItem>): void {\n items = items.map((i) => (i.id === id ? { ...i, ...changes } : i));\n}\n\nfunction isBusy(): boolean {\n return active !== null || currentId !== null;\n}\n\n/** Begin playing a specific item id, taking over any active playback. */\nasync function startItem(id: string): Promise<void> {\n const item = items.find((i) => i.id === id);\n if (!item) return;\n\n // Claim app-wide playback BEFORE producing audio — this stops any other\n // playback path (streaming auto-voice, etc.) so two voices can never overlap.\n // Re-claiming with our own id (item→item within the queue) is a no-op refresh.\n claimPlayback({\n id: QUEUE_HOLDER_ID,\n label: \"Playback queue\",\n stop: forcePreemptFromLock,\n });\n\n // Take over anything currently active.\n if (active) {\n const prev = active;\n active = null;\n try {\n await prev.stop();\n } catch {\n /* noop */\n }\n }\n\n const token = ++runToken;\n currentId = id;\n patch(id, { status: \"loading\", error: undefined });\n notify();\n\n const stale = () => token !== runToken;\n\n // The host's start gate (the web holds until an organization is set: speaking mints a provider\n // credential and reads the organization's listening knobs). A refusal fails this item only.\n const beforeStart = speechPorts().beforeStart;\n if (beforeStart) {\n try {\n await beforeStart(item);\n } catch (err) {\n if (stale()) return;\n patch(id, {\n status: \"error\",\n error:\n speechPorts().describeStartRefusal?.(err) ??\n (err instanceof Error ? err.message : \"Playback failed\"),\n });\n active = null;\n void advance();\n return;\n }\n if (stale()) return;\n }\n\n try {\n const adapter = await getAdapter(item.provider);\n if (stale()) return;\n const handle = await adapter.start(\n item,\n {\n onLoading: () => {\n if (stale()) return;\n patch(id, { status: \"loading\" });\n notify();\n },\n onPlaying: () => {\n if (stale()) return;\n patch(id, { status: \"playing\" });\n notify();\n },\n onEnded: () => {\n if (stale()) return;\n patch(id, { status: \"done\" });\n active = null;\n void advance();\n },\n onError: (message: string) => {\n if (stale()) return;\n patch(id, { status: \"error\", error: message });\n active = null;\n void advance();\n },\n },\n rate,\n );\n\n if (stale()) {\n // Taken over while we were connecting — discard this handle.\n try {\n await handle.stop();\n } catch {\n /* noop */\n }\n return;\n }\n active = handle;\n } catch (err) {\n if (stale()) return;\n patch(id, {\n status: \"error\",\n error: err instanceof Error ? err.message : \"Playback failed\",\n });\n active = null;\n void advance();\n }\n}\n\n/** Move to the next queued item, or go idle (releasing the playback lock). */\nasync function advance(): Promise<void> {\n const next = items.find((i) => i.status === \"queued\");\n if (next) {\n await startItem(next.id);\n } else {\n currentId = null;\n releasePlayback(QUEUE_HOLDER_ID);\n notify();\n }\n}\n\n/**\n * Called by the playback lock when ANOTHER path (e.g. streaming auto-voice)\n * takes over. Stop our audio and drop the queue — the user deliberately started\n * something else. Does NOT touch the lock (it already cleared us).\n */\nfunction forcePreemptFromLock(): void {\n runToken += 1;\n if (active) {\n const prev = active;\n active = null;\n try {\n void prev.stop();\n } catch {\n /* noop */\n }\n }\n items = [];\n currentId = null;\n notify();\n}\n\nexport interface EnqueueResult {\n id: string;\n}\n\n/** Add a request to the queue. Plays immediately if nothing is active. */\nexport function enqueuePlayback(request: PlaybackRequest): EnqueueResult {\n // First engagement mounts the lazy audio system (mirrors + panel UI). The\n // queue itself plays fine without it; the host replays state on mount.\n speechPorts().onEngage?.();\n // Manual enqueues run inside a tap — unlock iOS/WebKit output NOW, while\n // the gesture stack is live (audio itself starts after async work).\n primeAudioOutput();\n const id = nextId();\n const item: PlaybackItem = {\n ...request,\n id,\n status: \"queued\",\n enqueuedAtMs: Date.now(),\n };\n items = [...items, item];\n notify();\n if (!isBusy()) {\n void startItem(id);\n }\n return { id };\n}\n\nexport async function pausePlayback(): Promise<void> {\n if (!active || !currentId) return;\n await active.pause();\n patch(currentId, { status: \"paused\" });\n notify();\n}\n\nexport async function resumePlayback(): Promise<void> {\n speechPorts().onEngage?.();\n primeAudioOutput();\n if (!active || !currentId) return;\n await active.resume();\n patch(currentId, { status: \"playing\" });\n notify();\n}\n\n/** Stop the active item (marks it done) and advance to the next queued item. */\nexport async function skipPlayback(): Promise<void> {\n if (currentId) patch(currentId, { status: \"done\" });\n if (active) {\n const prev = active;\n active = null;\n try {\n await prev.stop();\n } catch {\n /* noop */\n }\n }\n await advance();\n}\n\n/** Play (or replay) a specific item now, taking over anything active. */\nexport async function playPlaybackItem(id: string): Promise<void> {\n speechPorts().onEngage?.();\n primeAudioOutput();\n const item = items.find((i) => i.id === id);\n if (!item) return;\n // Replay finished/errored items by resetting them to queued.\n if (item.status === \"done\" || item.status === \"error\") {\n patch(id, { status: \"queued\", error: undefined });\n }\n if (currentId && currentId !== id) {\n patch(currentId, { status: \"done\" });\n }\n await startItem(id);\n}\n\n/** Remove an item. If it's the active one, advance. */\nexport async function removePlaybackItem(id: string): Promise<void> {\n if (id === currentId) {\n await skipPlayback();\n items = items.filter((i) => i.id !== id);\n notify();\n return;\n }\n items = items.filter((i) => i.id !== id);\n notify();\n}\n\n/** Stop everything and clear the queue + history. */\nexport async function clearPlayback(): Promise<void> {\n runToken += 1;\n if (active) {\n const prev = active;\n active = null;\n try {\n await prev.stop();\n } catch {\n /* noop */\n }\n }\n items = [];\n currentId = null;\n releasePlayback(QUEUE_HOLDER_ID);\n notify();\n}\n\nexport function setPlaybackRate(value: number): void {\n rate = Math.max(0.25, Math.min(4, value));\n active?.setRate?.(rate);\n notify();\n}\n\nexport function getPlaybackSnapshot(): PlaybackSnapshot {\n return snapshot();\n}\n\n/** Test seam: drop every item and forget loaded engines. */\nexport function resetPlaybackForTests(): void {\n runToken += 1;\n active = null;\n items = [];\n currentId = null;\n rate = 1;\n adapterCache.clear();\n releasePlayback(QUEUE_HOLDER_ID);\n}\n\nexport function subscribePlayback(listener: Listener): () => void {\n listeners.add(listener);\n listener(snapshot());\n return () => {\n listeners.delete(listener);\n };\n}\n","/**\n * Playback Lock — app-wide single-OUTPUT arbitration.\n *\n * The output twin of `captureLock`. Just as only one mic capture may be live at\n * a time, **only one thing may be producing audible playback at any instant,\n * anywhere in the app.** Multiple playback paths exist (the unified\n * `playbackQueue` for Speaker buttons, the streaming auto-voice\n * `useCartesiaStreamingSpeaker`, xAI realtime, podcast PCM) and each one used to\n * play independently — so a War Room read-aloud could stream over a queued\n * utterance, two voices in your ear at once. This arbiter makes that\n * structurally impossible.\n *\n * Claiming is **start-always-wins**: a new claim instantly stops the current\n * holder (its `stop` runs synchronously) and takes ownership. Framework-free,\n * allocates nothing until first use, holds no audio itself — it only tracks who\n * currently owns playback and how to stop them.\n *\n * Contract for holders (mirror of captureLock):\n * - Stable id per path (constant for a singleton owner, `useId()` per instance).\n * - `claimPlayback({ id, stop })` right before producing audio.\n * - `releasePlayback(id)` when playback ends (finished, stopped, errored,\n * unmounted). Release is id-guarded — a stale release is a safe no-op.\n * - `stop` MUST be synchronous-effective (stop audio now).\n *\n * Subscribers get a `PlaybackTakeover` describing the holder change, which the\n * AudioPlaybackHost uses to surface the Audio panel whenever playback gets\n * \"complex\" (a cross-path takeover).\n */\n\nexport interface PlaybackHolder {\n /** Stable identifier for the claiming playback path / instance. */\n id: string;\n /** Optional human label for diagnostics / the panel indicator. */\n label?: string;\n /** Stop audio immediately. Called when another path takes over. */\n stop: () => void;\n}\n\nexport interface PlaybackTakeover {\n /** The holder that now owns playback, or null when playback went idle. */\n current: PlaybackHolder | null;\n /** The holder that was stopped to grant ownership, if this was a takeover. */\n preempted: PlaybackHolder | null;\n}\n\nlet current: PlaybackHolder | null = null;\nconst listeners = new Set<(event: PlaybackTakeover) => void>();\n\nfunction notify(preempted: PlaybackHolder | null): void {\n const event: PlaybackTakeover = { current, preempted };\n for (const cb of listeners) {\n try {\n cb(event);\n } catch (err) {\n console.error(\"[playbackLock] listener threw:\", err);\n }\n }\n}\n\n/**\n * Claim exclusive playback (start-always-wins). If a different holder currently\n * owns playback, its `stop()` runs first (synchronously) so two outputs can\n * never overlap. Re-claiming with the SAME id just refreshes the registration.\n */\nexport function claimPlayback(holder: PlaybackHolder): void {\n if (current && current.id === holder.id) {\n current = holder;\n notify(null);\n return;\n }\n let preempted: PlaybackHolder | null = null;\n if (current) {\n preempted = current;\n // Clear before stopping so a re-entrant claim/release from inside the\n // previous holder's stop() can't see a stale owner or loop.\n current = null;\n try {\n preempted.stop();\n } catch (err) {\n console.error(\n `[playbackLock] previous holder \"${preempted.id}\" stop() threw during takeover:`,\n err,\n );\n }\n }\n current = holder;\n notify(preempted);\n}\n\n/**\n * Release playback for `id`. Id-guarded: if `id` is not the current owner (it\n * was already taken over), this is a no-op.\n */\nexport function releasePlayback(id: string): void {\n if (current?.id === id) {\n current = null;\n notify(null);\n }\n}\n\n/** The id of the path currently holding playback, or null if idle. */\nexport function getActivePlaybackHolderId(): string | null {\n return current?.id ?? null;\n}\n\n/** True while any path holds playback. */\nexport function isPlaybackHeld(): boolean {\n return current !== null;\n}\n\n/** Subscribe to playback ownership changes. Returns an unsubscribe fn. */\nexport function subscribePlaybackLock(\n cb: (event: PlaybackTakeover) => void,\n): () => void {\n listeners.add(cb);\n return () => {\n listeners.delete(cb);\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEO,SAAS,gBAAgB,MAAkC;AAChE,UAAQ,EAAE,GAAG,OAAO,GAAG,MAAM,UAAU,EAAE,GAAG,MAAM,UAAU,GAAG,KAAK,SAAS,EAAE;AACjF;AAGO,SAAS,cAAoC;AAClD,SAAO;AACT;AAMO,SAAS,mBAAmB,MAAyB,oBAA6B;AACvF,SAAO,MAAM,GAAG,MAAM;AACxB;AAGO,SAAS,YAAyC,KAAqC;AAC5F,QAAM,QAAQ,MAAM,GAAG;AACvB,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,uDAAuD,OAAO,GAAG,CAAC,uBAAuB;AAAA,EAC3G;AACA,SAAO;AACT;AAGO,SAAS,2BAAiC;AAC/C,UAAQ,CAAC;AACX;AA9FA,IA+DI;AA/DJ;AAAA;AAAA;AA+DA,IAAI,QAA8B,CAAC;AAAA;AAAA;;;ACAnC,SAAS,8BAAoC;AAC3C,MAAI,qBAAsB;AAG1B,QAAM,UACJ,UACA;AACF,MAAI,SAAS;AACX,QAAI;AACF,cAAQ,OAAO;AACf,6BAAuB;AAAA,IACzB,QAAQ;AAAA,IAER;AAAA,EACF,OAAO;AACL,2BAAuB;AAAA,EACzB;AACF;AAMO,SAAS,0BAA+C;AAC7D,SAAO,iBAAiB,cAAc,UAAU,WAC5C,gBACA;AACN;AAOO,SAAS,wBAAiD;AAC/D,SAAO;AACT;AAMO,SAAS,mBAAyB;AACvC,MAAI,OAAO,WAAW,YAAa;AACnC,8BAA4B;AAG5B,MAAI;AACF,QAAI,CAAC,iBAAiB,cAAc,UAAU,UAAU;AACtD,sBAAgB,IAAI,aAAa;AAAA,IACnC;AACA,QAAI,cAAc,UAAU,aAAa;AACvC,WAAK,cAAc,OAAO,EAAE,MAAM,MAAM;AAAA,MAExC,CAAC;AAAA,IACH;AAEA,UAAM,SAAS,cAAc,aAAa,GAAG,GAAG,cAAc,UAAU;AACxE,UAAM,OAAO,cAAc,mBAAmB;AAC9C,SAAK,SAAS;AACd,SAAK,QAAQ,cAAc,WAAW;AACtC,SAAK,MAAM,CAAC;AAAA,EACd,QAAQ;AAAA,EAER;AAGA,MAAI;AACF,QAAI,CAAC,eAAe;AAClB,YAAMA,MAAK,IAAI,MAAM;AACrB,MAAAA,IAAG,aAAa,eAAe,MAAM;AACrC,sBAAgBA;AAAA,IAClB;AACA,UAAM,KAAK;AAEX,QAAI,GAAG,UAAU,CAAC,GAAG,IAAI,WAAW,OAAO,KAAK,CAAC,GAAG,WAAW,WAAW,MAAM,GAAG;AACjF,SAAG,QAAQ;AACX,SAAG,MAAM;AACT,WAAK,GACF,KAAK,EACL,KAAK,MAAM;AACV,WAAG,MAAM;AACT,WAAG,QAAQ;AAAA,MACb,CAAC,EACA,MAAM,MAAM;AACX,WAAG,QAAQ;AAAA,MACb,CAAC;AAAA,IACL;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAOO,SAAS,8BAAoC;AAClD,MAAI,sBAAsB,OAAO,WAAW,YAAa;AACzD,uBAAqB;AACrB,QAAM,YAAY,MAAM,iBAAiB;AACzC,SAAO,iBAAiB,eAAe,WAAW;AAAA,IAChD,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACD,SAAO,iBAAiB,WAAW,WAAW;AAAA,IAC5C,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AAGD,SAAO,iBAAiB,YAAY,WAAW;AAAA,IAC7C,SAAS;AAAA,IACT,SAAS;AAAA,EACX,CAAC;AACH;AAnLA,IAsDI,eACA,eACA,oBACA,sBAGE;AA5DN;AAAA;AAAA;AAsDA,IAAI,gBAAqC;AACzC,IAAI,gBAAyC;AAC7C,IAAI,qBAAqB;AACzB,IAAI,uBAAuB;AAG3B,IAAM,aACJ;AAAA;AAAA;;;AC1CF,SAAS,YAAY,MAAsB;AACzC,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;AACxC,QAAI,KAAC,6BAAc,OAAO,CAAC,GAAG;AAC5B,UAAI,KAAK,MAAM,CAAC,KAAK,EAAE;AACvB;AAAA,IACF;AACA,UAAM,UAAM,4BAAa,OAAO,CAAC;AACjC,UAAM,cAAU,wBAAS,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,+BAAiB,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC1F,UAAM,WAAW,MAAM,MAAM,IAAI,GAAG,GAAG,EAAE,OAAO,CAAC,SAAS,KAAK,KAAK,EAAE,SAAS,CAAC,EAAE;AAClF,UAAM,aACJ,QAAQ,SAAS,IACb,QAAQ,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,IAAI,WAAW,QAAQ,QAAQ,SAAS,CAAC,IACvE,QAAQ,WAAW,IACjB,QAAQ,CAAC,IACT;AACR,UAAM,UAAU,aAAa,IAAI,YAAY,WAAW,IAAI,GAAG,QAAQ,UAAU;AACjF,QAAI,KAAK,yBAAyB,OAAO,yBAAyB,UAAU,GAAG;AAC/E,QAAI,MAAM;AAAA,EACZ;AACA,SAAO,IAAI,KAAK,IAAI;AACtB;AAqFA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AA6CA,SAAS,0BACP,OACA,oBACA,SAAS,IACD;AACR,QAAM,eACJ,iCAAiC,mBAAmB,YAAY,CAAC;AACnE,MAAI,CAAC,aAAc,QAAO;AAC1B,MACE,6BAA6B,IAAI,YAAY,KAC7C,uBAAuB,cACvB;AACA,WAAO;AAAA,EACT;AACA,MAAI,eAAe,IAAI,YAAY,EAAG,QAAO,GAAG,YAAY,GAAG,MAAM;AACrE,QAAM,SAAS,MAAM,KAAK,aAAa,YAAY,CAAC,EAAE,KAAK,GAAG;AAI9D,SAAO,SAAS,GAAG,MAAM,OAAO;AAClC;AAmCO,SAAS,sBAAsB,MAAsB;AAC1D,MAAI,CAAC,QAAQ,CAAC,KAAK,SAAS,GAAG,EAAG,QAAO;AACzC,QAAM,QAAQ;AACd,SAAO,KACJ,MAAM,IAAI,EACV;AAAA,IAAI,CAAC,SACJ,mBAAmB,KAAK,IAAI,IACxB,OACA,KAAK,QAAQ,OAAO,IAAI,iBAAiB,GAAG;AAAA,EAClD,EACC,KAAK,IAAI;AACd;AAGA,SAAS,oBACP,MACA,OACQ;AACR,MAAI,MAAM;AACV,aAAW,EAAE,MAAM,GAAG,KAAK,OAAO;AAChC,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,CAAC,QAAQ,CAAC,GAAI;AAGlB,UAAM,KAAK,IAAI;AAAA,MACb,sBAAsB,aAAa,IAAI,CAAC;AAAA,MACxC;AAAA,IACF;AACA,UAAM,IAAI,QAAQ,IAAI,EAAE;AAAA,EAC1B;AACA,SAAO;AACT;AAEO,SAAS,oBACd,UACA,SACQ;AACR,MAAI,CAAC,YAAY,OAAO,aAAa,SAAU,QAAO;AAEtD,QAAM,gBAAgB,CAAC,QAAwB;AAC7C,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,OAAO;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,SAAS,SAAS,KAAK,EAAE;AAC/B,QAAI,SAAS,GAAI,QAAO,KAAK,MAAM,KAAK;AACxC,QAAI,SAAS,KAAK;AAChB,YAAM,IAAI,KAAK,MAAM,SAAS,EAAE;AAChC,YAAM,IAAI,SAAS;AACnB,aAAO,MAAM,IAAK,KAAK,CAAC,KAAK,MAAO,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE;AAAA,IACvE;AACA,QAAI,SAAS,KAAM;AACjB,YAAM,IAAI,KAAK,MAAM,SAAS,GAAG;AACjC,YAAM,YAAY,SAAS;AAC3B,aAAO,cAAc,IACjB,GAAG,KAAK,CAAC,CAAC,aACV,GAAG,KAAK,CAAC,CAAC,YAAY,cAAc,OAAO,SAAS,CAAC,CAAC;AAAA,IAC5D;AACA,WAAO,OAAO,MAAM;AAAA,EACtB;AAEA,QAAM,WAAsC;AAAA,IAC1C,aAAM;AAAA,IACN,aAAM;AAAA,IACN,gBAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,UAAK;AAAA,IACL,UAAK;AAAA,IACL,UAAK;AAAA,IACL,gBAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,mBAAO;AAAA,IACP,gBAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,UAAK;AAAA,IACL,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,UAAK;AAAA,IACL,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,mBAAO;AAAA,IACP,mBAAO;AAAA,IACP,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,IACN,aAAM;AAAA,EACR;AAEA,QAAM,mBAA8C;AAAA;AAAA,IAElD,KAAK;AAAA;AAAA,IACL,IAAI;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA;AAAA,IAEJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA;AAAA,IAEJ,KAAK;AAAA;AAAA,IACL,KAAK;AAAA;AAAA,IACL,KAAK;AAAA;AAAA;AAAA,IAEL,IAAI;AAAA;AAAA,IACJ,IAAI;AAAA;AAAA,IACJ,KAAK;AAAA;AAAA,IACL,IAAI;AAAA;AAAA,IACJ,MAAM;AAAA;AAAA,IACN,KAAK;AAAA;AAAA;AAAA,IAEL,MAAM;AAAA;AAAA,IACN,KAAK;AAAA;AAAA;AAAA,IAEL,KAAK;AAAA;AAAA,IACL,KAAK;AAAA;AAAA,IACL,IAAI;AAAA;AAAA,IACJ,KAAK;AAAA;AAAA,IACL,IAAI;AAAA;AAAA;AAAA,IAEJ,KAAK;AAAA;AAAA,IACL,KAAK;AAAA;AAAA,IACL,KAAK;AAAA;AAAA,EACP;AAEA,QAAM,qBAAqB,CAAC,UAA0B;AAGpD,WAAO,MACJ,QAAQ,8DAA8D,EAAE,EACxE,QAAQ,qDAAqD,EAAE;AAAA,EACpE;AAEA,QAAM,uBAAuB,CAAC,UAA0B;AACtD,QAAI,OAAO,MAAM,QAAQ,WAAW,EAAE,EAAE,QAAQ,gBAAgB,EAAE;AAIlE,WAAO,KAAK;AAAA,MACV;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAMA,QAAM,SAAS;AAAA,IACb,SAAS,gBAAgB,SACrB,oBAAoB,UAAU,QAAQ,cAAc,IACpD;AAAA,EACN;AAKA,MAAI,SAAS,gBAAY,mCAAgB,6BAAc,qBAAqB,mBAAmB,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM;AACrH,UAAM,WAAW,KAAK,YAAY;AAClC,QAAI,aAAa,UAAW,QAAO;AACnC,WAAO,sBAAsB,IAAI,QAAQ,IACrC,kBAAkB,QAAQ,oBAC1B;AAAA,EACN,CAAC,CAAC,CAAC,EAEA,MAAM,IAAI,EACV,IAAI,CAAC,aAAU,4BAAa,IAAI,KAAK,KAAK,QAAQ,EAAE,SAAS,GAAG,IAAI,KAAK,IAAK,EAC9E,KAAK,IAAI,EAET;AAAA,IACC;AAAA,IACA,CAAC,QAAQ,GAAG,MAAM,GAAG,cAAc,CAAC,CAAC,OAAO,cAAc,CAAC,CAAC;AAAA,EAC9D,EAEC;AAAA,IAAQ;AAAA,IAAqB,CAAC,IAAI,MACjC,UAAU,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,GAAG,EAAE,KAAK,CAAC;AAAA,EAC5C,EAEC,QAAQ,YAAY,EAAE,EACtB,QAAQ,UAAU,EAAE,EACpB,QAAQ,SAAS,EAAE,EACnB,QAAQ,SAAS,EAAE,EAEnB,QAAQ,qBAAqB,IAAI,EAEjC,QAAQ,kBAAkB,IAAI,EAE9B,QAAQ,cAAc,IAAI,EAE1B,QAAQ,cAAc,IAAI,EAE1B,QAAQ,4BAA4B,CAAC,QAAQ,MAAM,QAAQ;AAE1D,QAAI,IAAI,WAAW,SAAS,GAAG;AAC7B,aAAO,GAAG,IAAI;AAAA,IAChB;AAEA,UAAM,iBAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,UAAM,mBAAmB,eAAe;AAAA,MAAK,CAAC,QAC5C,IAAI,YAAY,EAAE,SAAS,GAAG;AAAA,IAChC;AACA,QAAI,kBAAkB;AACpB,aAAO,GAAG,IAAI;AAAA,IAChB;AACA,WAAO,GAAG,IAAI;AAAA,EAChB,CAAC,EAEA;AAAA,IAAQ;AAAA,IAA6B,CAAC,IAAI,QACzC,MAAM,GAAG,GAAG,sBAAsB;AAAA,EACpC,EAEC,QAAQ,gBAAgB,WAAW,EAEnC,QAAQ,wBAAwB,oBAAoB,EACpD,QAAQ,0BAA0B,kBAAkB,EAEpD,QAAQ,sBAAsB,IAAI,EAElC;AAAA,IACC;AAAA,IACA,CAAC,QAAQ,KAAK,YAAY,UAAU,cAAc,GAAG,CAAC,KAAK,OAAO;AAAA,EACpE,EAEC,QAAQ,gBAAgB,cAAc,EACtC,QAAQ,uBAAuB,eAAe,EAE9C,QAAQ,4BAA4B,EAAE,EAEtC,QAAQ,gBAAgB,6BAA6B,EACrD,QAAQ,oBAAoB,0BAA0B,EAEtD;AAAA,IACC;AAAA,IACA;AAAA,EACF,EAEC;AAAA,IACC;AAAA,IACA;AAAA,EACF,EAEC,QAAQ,mCAAmC,YAAY,EACvD,QAAQ,uCAAuC,UAAU,EACzD,QAAQ,kCAAkC,WAAW,EACrD,QAAQ,kCAAkC,QAAQ,EAElD;AAAA,IACC;AAAA,IACA,CAAC,QAAQ,MAAM,OAAO,QAAQ;AAC5B,YAAM,aAAa;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AACA,YAAM,YAAY,WAAW,SAAS,KAAK,IAAI,CAAC,KAAK;AACrD,aAAO,GAAG,SAAS,IAAI,GAAG,KAAK,IAAI;AAAA,IACrC;AAAA,EACF,EAEC;AAAA,IACC;AAAA,IACA,CAAC,QAAQ,MAAM,QAAQ,WAAW;AAChC,YAAM,UAAU,SAAS,IAAI;AAC7B,YAAM,aAAa,SACf,OAAO,YAAY,MAAM,OACvB,SACA,SACF;AACJ,aAAO,GAAG,OAAO,IAAI,MAAM,IAAI,UAAU,GAAG,KAAK;AAAA,IACnD;AAAA,EACF,EAEC;AAAA,IACC;AAAA,IACA,CAAC,UAAU;AACT,aAAO,SAAS,KAAK,KAAK;AAAA,IAC5B;AAAA,EACF,EAEC,QAAQ,6BAA6B,yBAAyB,EAE9D;AAAA,IACC;AAAA,IACA,CAAC,OAAO,QAAQ,SAAS;AACvB,YAAM,YAAY,KAAK,YAAY;AACnC,YAAM,WACJ,iBAAiB,SAAS,KAAK,iBAAiB,IAAI,KAAK;AAC3D,aAAO,GAAG,MAAM,IAAI,QAAQ;AAAA,IAC9B;AAAA,EACF,EAEC;AAAA,IACC;AAAA,IACA,CAAC,UAAU;AACT,YAAM,YAAY,MAAM,YAAY;AACpC,aAAO,iBAAiB,SAAS,KAAK,iBAAiB,KAAK,KAAK;AAAA,IACnE;AAAA,EACF,EAEC,QAAQ,MAAM,WAAW,EACzB,QAAQ,MAAM,sBAAsB,EACpC,QAAQ,MAAM,WAAW,EACzB,QAAQ,MAAM,SAAS,EACvB,QAAQ,MAAM,eAAe,EAC7B,QAAQ,MAAM,eAAe,EAC7B,QAAQ,MAAM,cAAc,EAC5B,QAAQ,MAAM,uBAAuB,EACrC,QAAQ,MAAM,0BAA0B,EACxC,QAAQ,MAAM,UAAU,EACxB,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,YAAY,EAC1B,QAAQ,MAAM,UAAU,EACxB,QAAQ,MAAM,YAAY,EAE1B,QAAQ,mBAAmB,EAAE,EAE7B,QAAQ,wBAAwB,OAAO,EAEvC,QAAQ,QAAQ,GAAG,EAEnB,KAAK;AAER,SAAO;AACT;AAtnBA,IAKA,eAuCM,uBA2BO,gCA2DP,kCAMA,6BASA,gBAkBA,8BAkDO;AArNb;AAAA;AAAA;AAKA,oBAQO;AA+BP,IAAM,wBAA6C,oBAAI,IAAI;AAAA,MACzD;AAAA,MAAc;AAAA,MAAM;AAAA,MAAc;AAAA,MAAM;AAAA,MAAU;AAAA,MAAM;AAAA,MAAQ;AAAA,MAAU;AAAA,MAAM;AAAA,MAAO;AAAA,MAAO;AAAA,MAC9F;AAAA,MAAM;AAAA,MAAQ;AAAA,MAAO;AAAA,MAAQ;AAAA,MAAS;AAAA,MAAU;AAAA,MAAS;AAAA,MAAO;AAAA,MAAQ;AAAA,MAAS;AAAA,MACjF;AAAA,MAAQ;AAAA,MAAO;AAAA,MAAQ;AAAA,MAAO;AAAA,MAAQ;AAAA,MAAO;AAAA,MAAY;AAAA,IAC3D,CAAC;AAuBM,IAAM,iCAAiC;AAAA,MAC5C,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,OAAO;AAAA,MACP,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AASA,IAAM,mCAAmC,OAAO;AAAA,MAC7C,OAAO,KAAK,8BAA8B,EAA2B;AAAA,QACpE,CAAC,iBAAiB,CAAC,aAAa,YAAY,GAAG,YAAY;AAAA,MAC7D;AAAA,IACF;AAEA,IAAM,8BAA8B,IAAI;AAAA,MACtC,OAAO,OAAO,KAAK,8BAA8B,EAC9C,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,IAAI,YAAY,EAChB,KAAK,GAAG,CAAC;AAAA,MACZ;AAAA,IACF;AAGA,IAAM,iBAAiB,oBAAI,IAAwB;AAAA,MACjD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAMD,IAAM,+BAA+B,oBAAI,IAAwB;AAAA,MAC/D;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AA0CM,IAAM,oBAAoB;AAAA;AAAA;;;AClK1B,SAAS,eACd,aACA,SACQ;AAGR,QAAM,KAAK,eAAe,IAAI,KAAK;AACnC,MAAI,KAAK,MAAM,wBAAyB,QAAO;AAC/C,SAAO,YAAY,YAAY,mBAAmB;AACpD;AAGO,SAAS,aAAa,WAA8C;AACzE,MACE,OAAO,cAAc,YACrB,aAAa,iBACb,aAAa,eACb;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAxEA,IAYa,cACA,sBAOA,kBACA,oBAMA,yBAKA,eACA,eACA,mBAEA,oBAMA;AA1Cb;AAAA;AAAA;AAYO,IAAM,eAAe;AACrB,IAAM,uBAAuB;AAO7B,IAAM,mBAAmB;AACzB,IAAM,qBAAqB;AAM3B,IAAM,0BAA0B;AAKhC,IAAM,gBAAgB;AACtB,IAAM,gBAAgB;AACtB,IAAM,oBAAoB;AAE1B,IAAM,qBAAqB;AAM3B,IAAM,0BAA0B;AAAA;AAAA;;;ACGhC,SAAS,oBAAoB,OAAyB;AAC3D,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,SAAS,EAAE;AAC3E,SAAO,qFAAqF,KAAK,OAAO;AAC1G;AAEA,eAAe,cAAc,SAAgF;AAC3G,QAAM,SAAS,YAAY,EAAE;AAC7B,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI;AACF,WAAO,MAAM,OAAO,OAAO;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAsRA,SAAS,aAAa,OAA2B;AAC/C,QAAM,UAAU,KAAK,KAAK;AAC1B,QAAM,QAAQ,IAAI,WAAW,QAAQ,MAAM;AAC3C,WAAS,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;AACtD,UAAM,KAAK,IAAI,QAAQ,WAAW,KAAK;AAAA,EACzC;AACA,SAAO;AACT;AAEA,SAAS,cAAc,OAAyC;AAC9D,SAAO,EAAE,IAAI,MAAM,GAAG;AACxB;AAEA,eAAe,qBACb,OACA,SAC+D;AAC/D,QAAM,EAAE,UAAU,eAAe,IAAI,MAAM,OAAO,8BAA8B;AAChF,QAAM,SAAS,IAAI,eAAe,EAAE,MAAM,CAAC;AAC3C,QAAM,SAAS,MAAM,OAAO,IAAI,UAAU;AAAA,IACxC,SAAS,EAAE,oBAAoB,qBAAqB;AAAA,EACtD,CAAC;AACD,QAAM,OAAO,QAAQ;AACrB,QAAM,iBAAiB,oBAAI,IAAgB;AAC3C,SAAO,OAAO,iBAAiB,SAAS,MAAM;AAC5C,eAAW,YAAY,eAAgB,UAAS;AAAA,EAClD,CAAC;AACD,SAAO;AAAA,IACL,IAAI,IAAI,iBAAiB,QAAQ,OAAO;AAAA,IACxC,KAAK;AAAA,MACH,IAAI,CAAC,QAAQ,aAAa;AACxB,uBAAe,IAAI,QAAQ;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AACF;AAGA,eAAsB,mBACpB,SACgE;AAChE,QAAM,YAAY,EAAE,GAAG,UAAU,GAAG,QAAQ;AAC5C,MAAI,UAAU,cAAc,OAAO;AACjC,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,MAAI,UAAU,aAAa,aAAa;AACtC,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACA,QAAM,cAAiC,CAAC,KAAM,MAAO,OAAO,MAAO,OAAO,IAAK;AAC/E,MAAI,CAAC,YAAY,SAAS,UAAU,UAAU,GAAG;AAC/C,UAAM,IAAI,MAAM,qCAAqC,UAAU,UAAU,EAAE;AAAA,EAC7E;AACA,QAAM,WAAW;AACjB,QAAM,WAAW,YAAY,kBAAkB;AAC/C,QAAM,QAAQ,MAAM,SAAS;AAC7B,MAAI;AACF,QAAI;AACF,aAAO,MAAM,qBAAqB,OAAO,QAAQ;AAAA,IACnD,SAAS,OAAO;AACd,UAAI,CAAC,oBAAoB,KAAK,EAAG,OAAM;AACvC,kBAAY,EAAE,0BAA0B,KAAK;AAC7C,YAAM,YAAY,MAAM,SAAS,EAAE,cAAc,KAAK,CAAC,EAAE;AAAA,QACvD,CAAC,cAAuB;AACtB,gBAAM,IAAI,kBAAkB,SAAS;AAAA,QACvC;AAAA,MACF;AACA,aAAO,MAAM,qBAAqB,WAAW,QAAQ;AAAA,IACvD;AAAA,EACF,SAAS,OAAO;AAGd,QAAI,iBAAiB,kBAAmB,OAAM,MAAM;AACpD,UAAM,MAAM,6BAA6B,KAAK;AAAA,EAChD;AACF;AASA,eAAe,6BACb,OAC+B;AAC/B,QAAM,MACJ,iBAAiB,QACb,MAAM,UACN,OAAO,UAAU,YAAY,SAAS,aAAa,QACjD,OAAQ,MAA+B,OAAO,IAC9C;AACR,QAAM,SAAS,iBAAiB,KAAK,GAAG,IAAI,CAAC;AAC7C,QAAM,UAAkC;AAAA,IACtC,UAAU;AAAA,IACV,aAAa,SAAS,OAAO,MAAM,IAAI;AAAA,IACvC,SAAS,OAAO;AAAA,EAClB;AACA,QAAM,UAAU,MAAM,cAAc,OAAO;AAC3C,SAAO,IAAI;AAAA,IACT;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,EAAE,OAAO,MAAM;AAAA,EACjB;AACF;AAzbA,IAsBa,sBAuEA,qBAyEP,UAYA,kBA2OA;AA7ZN;AAAA;AAAA;AAeA;AACA;AAMO,IAAM,uBAAN,cAAmC,MAAM;AAAA,MAC5B,OAAO;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MAET,YACE,SACA,SACA,iBACA,SACA;AACA,cAAM,SAAS,gBAAgB,iBAAiB,OAAO;AACvD,aAAK,WAAW,QAAQ;AAExB,aAAK,YAAY,UAAU,QAAQ,YAAY;AAC/C,aAAK,YAAY,SAAS,cAAc,QAAQ,cAAc;AAC9D,aAAK,WAAW,YAAY;AAAA,MAC9B;AAAA,IACF;AAmDO,IAAM,sBAAN,MAA0B;AAAA,MACtB;AAAA,MACT,UAA0B,CAAC;AAAA,MAC3B,QAAQ;AAAA,MACR,UAA+B;AAAA,MAE/B,YAAY,YAAoB;AAC9B,aAAK,aAAa;AAAA,MACpB;AAAA,MAEA,sBAAsB,cAA8B;AAClD,eAAO,KAAK,KAAK,eAAe,KAAK,UAAU;AAAA,MACjD;AAAA,MAEA,KAAK,OAAyB;AAC5B,cAAM,cAAc,KAAK,MAAM,MAAM,aAAa,aAAa,iBAAiB;AAChF,cAAM,UAAU,IAAI,aAAa,WAAW;AAC5C,cAAM,OAAO,IAAI,SAAS,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU;AAC1E,iBAAS,QAAQ,GAAG,QAAQ,aAAa,SAAS,GAAG;AACnD,kBAAQ,KAAK,IAAI,KAAK,WAAW,QAAQ,aAAa,mBAAmB,IAAI;AAAA,QAC/E;AACA,aAAK,QAAQ,KAAK,OAAO;AACzB,aAAK,MAAM;AAAA,MACb;AAAA,MAEA,SAAe;AACb,aAAK,QAAQ;AACb,aAAK,MAAM;AAAA,MACb;AAAA,MAEA,MAAM,KAAK,aAA4C;AACrD,YAAI,UAAU;AACd,eAAO,UAAU,YAAY,QAAQ;AACnC,iBAAO,KAAK,QAAQ,WAAW,KAAK,CAAC,KAAK,OAAO;AAC/C,kBAAM,IAAI,QAAc,CAAC,YAAY;AACnC,mBAAK,UAAU;AAAA,YACjB,CAAC;AAAA,UACH;AACA,cAAI,KAAK,QAAQ,WAAW,EAAG;AAC/B,gBAAM,QAAQ,KAAK,QAAQ,CAAC;AAC5B,gBAAM,QAAQ,KAAK,IAAI,MAAM,QAAQ,YAAY,SAAS,OAAO;AACjE,sBAAY,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO;AACjD,qBAAW;AACX,cAAI,UAAU,MAAM,QAAQ;AAC1B,iBAAK,QAAQ,MAAM;AAAA,UACrB,OAAO;AACL,iBAAK,QAAQ,CAAC,IAAI,MAAM,SAAS,KAAK;AAAA,UACxC;AAAA,QACF;AACA,eAAO;AAAA,MACT;AAAA,MAEA,QAAc;AACZ,aAAK,UAAU;AACf,aAAK,UAAU;AAAA,MACjB;AAAA,IACF;AAiBA,IAAM,WAA+C;AAAA,MACnD,WAAW;AAAA,MACX,UAAU;AAAA,MACV,YAAY;AAAA,IACd;AAQA,IAAM,mBAAN,MAAoD;AAAA,MAClD;AAAA,MACA;AAAA,MACA,WAAW,oBAAI,IAAiC;AAAA,MAChD,aAAa,oBAAI,IAAkC;AAAA;AAAA,MAGnD,WAAwC;AAAA;AAAA,MAExC,cAAc,oBAAI,IAAoB;AAAA,MAEtC,YAAY,QAAsB,SAAgC;AAChE,aAAK,UAAU;AACf,aAAK,WAAW;AAIhB,eAAO,GAAG,SAAS,MAAM;AAAA,QAAC,CAAC;AAC3B,eAAO,GAAG,SAAS,CAAC,UAAU;AAC5B,gBAAM,YAAY,gBAAgB,QAAQ,MAAM,aAAa;AAC7D,cAAI,MAAM,SAAS,SAAS;AAI1B,kBAAMC,aAAY,YAAY,KAAK,WAAW,IAAI,SAAS,IAAI;AAC/D,gBAAI,WAAW;AACb,mBAAK,SAAS,IAAI,SAAS,GAAG,OAAO;AACrC,mBAAK,SAAS,OAAO,SAAS;AAC9B,mBAAK,WAAW,OAAO,SAAS;AAAA,YAClC,OAAO;AACL,yBAAWC,WAAU,KAAK,SAAS,OAAO,EAAG,CAAAA,QAAO,OAAO;AAC3D,mBAAK,SAAS,MAAM;AACpB,mBAAK,WAAW,MAAM;AAAA,YACxB;AACA,iBAAK,KAAK,qBAAqB,OAAOD,UAAS;AAC/C;AAAA,UACF;AACA,cAAI,CAAC,UAAW;AAChB,gBAAM,SAAS,KAAK,SAAS,IAAI,SAAS;AAC1C,cAAI,MAAM,SAAS,WAAW,QAAQ;AACpC,mBAAO,KAAK,MAAM,SAAS,aAAa,MAAM,IAAI,CAAC;AAAA,UACrD,WAAW,MAAM,SAAS,QAAQ;AAChC,oBAAQ,OAAO;AAAA,UACjB;AACA,gBAAM,aAAa,KAAK,UAAU,KAAK;AACvC,qBAAW,YAAY,KAAK,WAAW,IAAI,SAAS,KAAK,CAAC,GAAG;AAC3D,qBAAS,UAAU;AAAA,UACrB;AACA,cAAI,MAAM,SAAS,QAAQ;AACzB,iBAAK,SAAS,OAAO,SAAS;AAC9B,iBAAK,WAAW,OAAO,SAAS;AAAA,UAClC;AAAA,QACF,CAAC;AAAA,MACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAM,qBACJ,OACAA,YACe;AACf,cAAM,UAAkC;AAAA,UACtC,UAAU;AAAA,UACV,aAAa,MAAM,eAAe;AAAA,UAClC,YAAY,MAAM,cAAc,MAAM,SAAS;AAAA,UAC/C,SAAS,MAAM,WAAW,MAAM,SAAS;AAAA,QAC3C;AACA,cAAM,UAAU,MAAM,cAAc,OAAO;AAC3C,cAAM,QAAQ,IAAI,qBAAqB,SAAS,SAAS,QAAQ,OAAO;AACxE,YAAI,CAAC,MAAM,UAAW,MAAK,WAAW;AACtC,cAAM,YAAY,KAAK,UAAU;AAAA,UAC/B,GAAG;AAAA,UACH,SAAS,MAAM;AAAA,UACf,WAAW,MAAM;AAAA,QACnB,CAAC;AACD,mBAAW,YAAYA,cAAa,CAAC,EAAG,UAAS,SAAS;AAG1D,oBAAY,EAAE,cAAc,kBAAkB,MAAM,OAAO;AAAA,MAC7D;AAAA,MAEA,MAAM,KAAK,SAA2D;AAGpE,YAAI,KAAK,SAAU,OAAM,KAAK;AAC9B,cAAM,YAAY,QAAQ,aAAa,OAAO,WAAW;AACzD,YAAI,SAAS,KAAK,SAAS,IAAI,SAAS;AACxC,YAAI,CAAC,QAAQ;AACX,mBAAS,IAAI,oBAAoB,KAAK,SAAS,UAAU;AACzD,eAAK,SAAS,IAAI,WAAW,MAAM;AAAA,QACrC;AAEA,cAAM,OAAO,KAAK,QAAQ,KAAM;AAAA,UAC9B,YAAY;AAAA,UACZ,UAAU,QAAQ;AAAA,UAClB,YAAY,QAAQ;AAAA,UACpB,OAAO,cAAc,QAAQ,KAAK;AAAA,UAClC,eAAe;AAAA,YACb,WAAW,KAAK,SAAS;AAAA,YACzB,UAAU,KAAK,SAAS;AAAA,YACxB,aAAa,KAAK,SAAS;AAAA,UAC7B;AAAA,UACA,UAAU,QAAQ;AAAA,UAClB,UAAU,QAAQ;AAAA,UAClB,gBAAgB,QAAQ;AAAA,UACxB,wBAAwB,QAAQ;AAAA,UAChC,qBAAqB,QAAQ;AAAA,UAC7B,mBAAmB,QAAQ;AAAA,QAC7B,CAAyC;AACzC,cAAM;AACN,aAAK,aAAa,WAAW,OAAO;AAEpC,eAAO;AAAA,UACL;AAAA,UACA,IAAI,CAAC,QAAQ,aAAa;AACxB,kBAAMA,aAAY,KAAK,WAAW,IAAI,SAAS,KAAK,oBAAI,IAAqB;AAC7E,YAAAA,WAAU,IAAI,QAAQ;AACtB,iBAAK,WAAW,IAAI,WAAWA,UAAS;AAAA,UAC1C;AAAA,QACF;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,aAAa,WAAmB,SAAmC;AACjE,cAAM,aAAa,QAAQ,YAAY,UAAU;AACjD,YAAI,cAAc,EAAG;AACrB,cAAM,YAAY,KAAK,YAAY,IAAI,SAAS,KAAK,KAAK;AAC1D,aAAK,YAAY,IAAI,WAAW,QAAQ;AACxC,YAAI;AACF,sBAAY,EAAE,sBAAsB;AAAA,YAClC,UAAU;AAAA,YACV,OAAO,QAAQ;AAAA,YACf,UAAU,GAAG,SAAS,IAAI,QAAQ;AAAA,YAClC;AAAA,UACF,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,MAEA,SAAS,SAA2D;AAClE,eAAO,KAAK,KAAK,OAAO;AAAA,MAC1B;AAAA,MAEA,aAAmB;AACjB,mBAAW,UAAU,KAAK,SAAS,OAAO,EAAG,QAAO,OAAO;AAC3D,aAAK,SAAS,MAAM;AACpB,aAAK,WAAW,MAAM;AACtB,aAAK,QAAQ,MAAM;AAAA,MACrB;AAAA,IACF;AA+EA,IAAM,oBAAN,cAAgC,MAAM;AAAA,MACpC,YAA8B,OAAgB;AAC5C,cAAM,oBAAoB;AADE;AAAA,MAE9B;AAAA,MAF8B;AAAA,IAGhC;AAAA;AAAA;;;ACvXO,SAAS,4BAAqC;AACnD,SACE,OAAO,qBAAqB,eAC5B,eAAe,iBAAiB;AAEpC;AAGO,SAAS,4BAAqC;AACnD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,OACJ,OAAO,gBACN,OACE;AACL,SAAO,CAAC,CAAC,QAAQ,eAAe,KAAK;AACvC;AAOO,SAAS,2BAAoC;AAClD,SAAO,0BAA0B,KAAK,0BAA0B;AAClE;AAGO,SAAS,6BAAqC;AACnD,SAAO,MAAM;AACf;AAOO,SAAS,2BAA2B,UAAwB;AACjE,MAAI,MAAM,aAAa,SAAU;AACjC,QAAM,WAAW;AACjB,aAAW,KAAK,MAAM,WAAW;AAC/B,QAAI;AACF,QAAE,QAAQ;AAAA,IACZ,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAGO,SAAS,sBAAsB,UAAoC;AACxE,QAAM,UAAU,IAAI,QAAQ;AAC5B,SAAO,MAAM;AACX,UAAM,UAAU,OAAO,QAAQ;AAAA,EACjC;AACF;AAQA,eAAsB,wBACpB,IACe;AACf,MAAI,CAAC,GAAI;AACT,MAAI,CAAC,0BAA0B,EAAG;AAClC,QAAM,SAAS,MAAM;AAErB,QAAM,aAAa;AAInB,MAAI,WAAW,WAAW,OAAQ;AAClC,MAAI;AACF,UAAM,WAAW,YAAY,MAAM;AAAA,EACrC,SAAS,KAAK;AAEZ,YAAQ;AAAA,MACN;AAAA,MAEA,EAAE,UAAU,QAAQ,OAAO,IAAI;AAAA,IACjC;AAAA,EACF;AACF;AA9HA,IAoCM;AApCN;AAAA;AAAA;AAoCA,IAAM,QAAmB;AAAA,MACvB,UAAU;AAAA,MACV,WAAW,oBAAI,IAAI;AAAA,IACrB;AAAA;AAAA;;;ACoDA,SAAS,qBAAqB,YAA2C;AACvE,SAAO,IAAI,aAAa,EAAE,WAAW,CAAC;AACxC;AA7FA,IA+Fa;AA/Fb;AAAA;AAAA;AA2CA;AAIA;AAgDO,IAAM,kBAAN,MAAsB;AAAA,MAC3B,WAAyC;AAAA,MACzC,uBAAuB;AAAA,MACvB;AAAA,MACA;AAAA,MACA,mBAAwC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUxC,cAAc;AAAA,MACd,iBAAkC;AAAA,MAClC,eAAe,oBAAI,IAA2B;AAAA,MAC9C,WAAW;AAAA,MAEX,YAAY,EAAE,gBAAgB,cAAc,GAA2B;AACrE,aAAK,kBAAkB;AACvB,aAAK,iBAAiB,iBAAiB;AAAA,MACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWA,MAAM,KAAK,QAAuC;AAChD,aAAK,uBAAuB;AAC5B,aAAK,WAAW;AAChB,aAAK,wBAAwB;AAE7B,cAAM,SAAS,KAAK,mBAAmB,uBACnC,wBAAwB,IACxB;AACJ,cAAM,UACJ,UAAU,KAAK,eAAe,OAAO,UAAU;AACjD,aAAK,cAAc,WAAW;AAC9B,aAAK,WAAW;AAChB,YAAI,KAAK,aAAa;AACpB,gBAAM,MAAM;AACZ,eAAK,iBAAiB,IAAI,WAAW;AACrC,eAAK,eAAe,QAAQ,IAAI,WAAW;AAAA,QAC7C,OAAO;AACL,eAAK,iBAAiB;AAAA,QACxB;AACA,aAAK,mBAAmB,OAAO;AAQ/B,YAAK,QAAQ,UAAqB,WAAW;AAC3C,cAAI;AACF,kBAAM,QAAQ,OAAO;AAAA,UACvB,QAAQ;AAAA,UAER;AACA,cAAK,QAAQ,UAAqB,WAAW;AAC3C,kBAAM,IAAI;AAAA,cACR;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAEA,YAAI;AACF,gBAAM,SAAS,IAAI;AAAA,YACjB,OAAO,sBAAsB,KAAK,eAAe;AAAA,UACnD;AACA,gBAAM,QAAyB,CAAC;AAChC,iBAAO,MAAM;AACX,kBAAM,OAAO,MAAM,OAAO,KAAK,MAAM;AAGrC,kBAAM,gBAAgB,OAAO,SAAS,GAAG,IAAI;AAC7C,kBAAM,KAAK,KAAK,YAAY,eAAe,OAAO,UAAU,CAAC;AAC7D,gBAAI,OAAO,OAAO,OAAQ;AAAA,UAC5B;AACA,gBAAM,QAAQ,IAAI,KAAK;AAAA,QACzB,UAAE;AAIA,eAAK,wBAAwB;AAAA,QAC/B;AAAA,MACF;AAAA;AAAA,MAGA,MAAM,QAAuB;AAC3B,cAAM,UAAU,KAAK,gBAAgB;AACrC,YAAI,KAAK,eAAe,KAAK,SAAU;AACvC,cAAM,QAAQ,QAAQ;AAAA,MACxB;AAAA;AAAA,MAGA,MAAM,SAAwB;AAC5B,cAAM,UAAU,KAAK,gBAAgB;AACrC,YAAI,KAAK,eAAe,KAAK,SAAU;AACvC,cAAM,QAAQ,OAAO;AAAA,MACvB;AAAA;AAAA,MAGA,MAAM,SAAwB;AAC5B,YAAI,KAAK,gBAAgB,EAAE,UAAU,WAAW;AAC9C,gBAAM,KAAK,MAAM;AAAA,QACnB,OAAO;AACL,gBAAM,KAAK,OAAO;AAAA,QACpB;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYA,MAAM,OAAsB;AAC1B,cAAM,UAAU,KAAK,gBAAgB;AACrC,aAAK,wBAAwB;AAC7B,YAAI,KAAK,aAAa;AACpB,cAAI,KAAK,SAAU;AACnB,eAAK,WAAW;AAChB,qBAAW,QAAQ,KAAK,cAAc;AACpC,gBAAI;AACF,mBAAK,KAAK;AAAA,YACZ,QAAQ;AAAA,YAER;AAAA,UACF;AACA,eAAK,aAAa,MAAM;AACxB,cAAI;AACF,iBAAK,gBAAgB,WAAW;AAAA,UAClC,QAAQ;AAAA,UAER;AACA,eAAK,iBAAiB;AACtB,cAAI,QAAQ,UAAU,aAAa;AACjC,kBAAM,QAAQ,OAAO,EAAE,MAAM,MAAM;AAAA,YAAC,CAAC;AAAA,UACvC;AACA;AAAA,QACF;AACA,YAAI,QAAQ,UAAU,SAAU;AAChC,cAAM,QAAQ,MAAM;AAAA,MACtB;AAAA,MAEA,kBAAyC;AACvC,YAAI,CAAC,KAAK,UAAU;AAClB,gBAAM,IAAI,MAAM,+BAA+B;AAAA,QACjD;AACA,eAAO,KAAK;AAAA,MACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA,mBAAmB,SAAsC;AACvD,YAAI,OAAO,QAAQ,cAAc,WAAY;AAE7C,cAAM,YAAY,CAAC,aAAqB;AACtC,cAAI,KAAK,aAAa,WAAW,QAAQ,UAAU,SAAU;AAG7D,kBAAQ,YAAY,QAAQ,EAAE,MAAM,CAAC,QAAiB;AACpD,oBAAQ;AAAA,cACN;AAAA,cAEA,EAAE,UAAU,OAAO,IAAI;AAAA,YACzB;AAAA,UACF,CAAC;AAAA,QACH;AAIA,cAAM,UAAU,2BAA2B;AAC3C,YAAI,QAAS,WAAU,OAAO;AAE9B,aAAK,mBAAmB,sBAAsB,SAAS;AAAA,MACzD;AAAA,MAEA,0BAAgC;AAC9B,aAAK,mBAAmB;AACxB,aAAK,mBAAmB;AAAA,MAC1B;AAAA,MAEA,MAAM,YAAY,KAAmB,YAAmC;AACtE,cAAM,UAAU,KAAK,gBAAgB;AACrC,YAAI,IAAI,WAAW,EAAG;AACtB,YAAI,KAAK,eAAe,KAAK,SAAU;AACvC,cAAM,UAAU,KAAK;AACrB,cAAM,WAAW,IAAI,SAAS;AAC9B,aAAK,uBACH,WAAW,KAAK,IAAI,QAAQ,aAAa,KAAK,oBAAoB;AAEpE,cAAM,aAAa,QAAQ,mBAAmB;AAG9C,cAAM,cAAc,QAAQ,aAAa,GAAG,IAAI,QAAQ,UAAU;AAClE,oBAAY,eAAe,CAAC,EAAE,IAAI,GAAG;AACrC,mBAAW,SAAS;AACpB,mBAAW,QAAQ,KAAK,kBAAkB,QAAQ,WAAW;AAC7D,mBAAW,MAAM,OAAO;AACxB,YAAI,KAAK,YAAa,MAAK,aAAa,IAAI,UAAU;AACtD,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,qBAAW,UAAU,MAAM;AACzB,iBAAK,aAAa,OAAO,UAAU;AACnC,oBAAQ;AAAA,UACV;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA;AAAA;;;AChUA;AAAA;AAAA;AAAA;AAAA;AAuBO,SAAS,sBAAsB,MAIkB;AACtD,QAAM,MAA2D;AAAA,IAC/D,OAAO,aAAa,MAAM,KAAK;AAAA,IAC/B,QAAQ,MAAM,UAAU;AAAA,EAC1B;AACA,MAAI,MAAM,QAAS,KAAI,UAAU,KAAK,QAAQ,YAAY;AAC1D,SAAO;AACT;AAEA,eAAe,YAAY,MAAqC;AAC9D,MAAI,iBAAwC,CAAC;AAE7C,MAAI;AACF,qBAAkB,MAAM,YAAY,EAAE,wBAAwB,IAAI,KAAM,CAAC;AAAA,EAC3E,QAAQ;AACN,qBAAiB,CAAC;AAAA,EACpB;AACA,QAAM,kBAAkB,KAAK,mBAAmB;AAChD,SAAO,kBACH,oBAAoB,KAAK,MAAM,eAAe,SAAS,EAAE,eAAe,IAAI,MAAS,IACrF,KAAK;AACX;AAhDA,IAkDa;AAlDb;AAAA;AAAA;AAcA;AACA;AAEA;AACA;AACA;AA+BO,IAAM,kBAAmC;AAAA,MAC9C,UAAU;AAAA,MAEV,MAAM,MACJ,MACA,IACyB;AACzB,WAAG,UAAU;AAQb,cAAM,YAAY,KAAK,YAAY,CAAC;AACpC,cAAM,WAAW,MAAM,YAAY,sBAAsB,EAAE;AAC3D,cAAM,QAAQ;AAAA,UACZ,SACE,UAAU,WACV,eAAe,SAAS,OAAO,UAAU,WAAW,WAAW;AAAA,UACjE,UAAU,UAAU,YAAY,SAAS;AAAA,UACzC,OAAO,UAAU,SAAS,SAAS;AAAA,UACnC,SAAS,SAAS;AAAA,QACpB;AAEA,cAAM,aAAa,MAAM,YAAY,IAAI,GAAG,KAAK;AACjD,YAAI,CAAC,WAAW;AACd,gBAAM,IAAI,MAAM,kBAAkB;AAAA,QACpC;AAGA,cAAM,EAAE,GAAG,IAAI,MAAM,mBAAmB;AAExC,YAAI,UAAU;AACd,cAAM,SAAS,IAAI,gBAAgB;AAAA,UACjC,gBAAgB;AAAA,QAClB,CAAC;AAED,cAAM,UAAU,MAAM;AACpB,cAAI;AACF,eAAG,WAAW;AAAA,UAChB,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,cAAM,WAAW,MAAM,GAAG,KAAK;AAAA,UAC7B,SAAS;AAAA,UACT,OAAO,EAAE,MAAM,MAAe,IAAI,MAAM,QAAQ;AAAA,UAChD,UAAW,MAAM,YAAY;AAAA,UAC7B,YAAY;AAAA,UACZ,kBAAkB,sBAAsB,EAAE,OAAO,MAAM,OAAO,SAAS,MAAM,WAAW,KAAK,CAAC;AAAA,QAChG,CAAC;AAED,YAAI,SAAS;AACX,kBAAQ;AACR,gBAAM,IAAI,MAAM,sCAAsC;AAAA,QACxD;AAEA,WAAG,UAAU;AAGb,eACG,KAAK,SAAS,MAAM,EACpB,KAAK,MAAM;AACV,cAAI,QAAS;AACb,kBAAQ;AACR,aAAG,QAAQ;AAAA,QACb,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,cAAI,QAAS;AACb,kBAAQ;AACR,aAAG,QAAQ,eAAe,QAAQ,IAAI,UAAU,iBAAiB;AAAA,QACnE,CAAC;AAEH,eAAO;AAAA,UACL,OAAO,MAAM,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,UAC1C,QAAQ,MAAM,OAAO,OAAO,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,UAC5C,MAAM,YAAY;AAChB,sBAAU;AACV,gBAAI;AACF,oBAAM,OAAO,KAAK;AAAA,YACpB,QAAQ;AAAA,YAER;AACA,oBAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA;AAAA;;;AC5IA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA;;;ACKA;;;ACKA;AACA;;;AC4BA,IAAI,UAAiC;AACrC,IAAM,YAAY,oBAAI,IAAuC;AAE7D,SAAS,OAAO,WAAwC;AACtD,QAAM,QAA0B,EAAE,SAAS,UAAU;AACrD,aAAW,MAAM,WAAW;AAC1B,QAAI;AACF,SAAG,KAAK;AAAA,IACV,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAkC,GAAG;AAAA,IACrD;AAAA,EACF;AACF;AAOO,SAAS,cAAc,QAA8B;AAC1D,MAAI,WAAW,QAAQ,OAAO,OAAO,IAAI;AACvC,cAAU;AACV,WAAO,IAAI;AACX;AAAA,EACF;AACA,MAAI,YAAmC;AACvC,MAAI,SAAS;AACX,gBAAY;AAGZ,cAAU;AACV,QAAI;AACF,gBAAU,KAAK;AAAA,IACjB,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN,mCAAmC,UAAU,EAAE;AAAA,QAC/C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,YAAU;AACV,SAAO,SAAS;AAClB;AAMO,SAAS,gBAAgB,IAAkB;AAChD,MAAI,SAAS,OAAO,IAAI;AACtB,cAAU;AACV,WAAO,IAAI;AAAA,EACb;AACF;AAGO,SAAS,4BAA2C;AACzD,SAAO,SAAS,MAAM;AACxB;AAGO,SAAS,iBAA0B;AACxC,SAAO,YAAY;AACrB;AAGO,SAAS,sBACd,IACY;AACZ,YAAU,IAAI,EAAE;AAChB,SAAO,MAAM;AACX,cAAU,OAAO,EAAE;AAAA,EACrB;AACF;;;ADzFA,IAAM,kBAAkB;AAMxB,IAAM,eAAe,oBAAI,IAAuC;AAEhE,eAAe,WACb,UAC0B;AAC1B,QAAM,SAAS,aAAa,IAAI,QAAQ;AACxC,MAAI,OAAQ,QAAO;AACnB,MAAI;AACJ,QAAM,aAAa,YAAY,EAAE,WAAW,QAAQ;AACpD,MAAI,YAAY;AACd,cAAU,MAAM,WAAW;AAAA,EAC7B,WAAW,aAAa,YAAY;AAClC,eAAW,MAAM,mFAA8B;AAAA,EACjD,OAAO;AACL,UAAM,IAAI,MAAM,qBAAqB,QAAQ,0BAA0B;AAAA,EACzE;AACA,eAAa,IAAI,UAAU,OAAO;AAClC,SAAO;AACT;AAIA,IAAI,QAAwB,CAAC;AAC7B,IAAI,YAA2B;AAC/B,IAAI,OAAO;AACX,IAAI,SAAgC;AACpC,IAAI,WAAW;AACf,IAAME,aAAY,oBAAI,IAAc;AAEpC,IAAI,MAAM;AACV,SAAS,SAAiB;AACxB,SAAO;AACP,SAAO,MAAM,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,GAAG;AAC7C;AAEA,SAAS,WAA6B;AACpC,SAAO,EAAE,OAAO,MAAM,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,GAAG,WAAW,KAAK;AAChE;AAEA,SAASC,UAAe;AACtB,QAAM,OAAO,SAAS;AACtB,EAAAD,WAAU,QAAQ,CAAC,MAAM;AACvB,QAAI;AACF,QAAE,IAAI;AAAA,IACR,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAkC,GAAG;AAAA,IACrD;AAAA,EACF,CAAC;AACH;AAEA,SAAS,MAAM,IAAY,SAAsC;AAC/D,UAAQ,MAAM,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,GAAG,QAAQ,IAAI,CAAE;AACnE;AAEA,SAAS,SAAkB;AACzB,SAAO,WAAW,QAAQ,cAAc;AAC1C;AAGA,eAAe,UAAU,IAA2B;AAClD,QAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC1C,MAAI,CAAC,KAAM;AAKX,gBAAc;AAAA,IACZ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,MAAM;AAAA,EACR,CAAC;AAGD,MAAI,QAAQ;AACV,UAAM,OAAO;AACb,aAAS;AACT,QAAI;AACF,YAAM,KAAK,KAAK;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,QAAQ,EAAE;AAChB,cAAY;AACZ,QAAM,IAAI,EAAE,QAAQ,WAAW,OAAO,OAAU,CAAC;AACjD,EAAAC,QAAO;AAEP,QAAM,QAAQ,MAAM,UAAU;AAI9B,QAAM,cAAc,YAAY,EAAE;AAClC,MAAI,aAAa;AACf,QAAI;AACF,YAAM,YAAY,IAAI;AAAA,IACxB,SAAS,KAAK;AACZ,UAAI,MAAM,EAAG;AACb,YAAM,IAAI;AAAA,QACR,QAAQ;AAAA,QACR,OACE,YAAY,EAAE,uBAAuB,GAAG,MACvC,eAAe,QAAQ,IAAI,UAAU;AAAA,MAC1C,CAAC;AACD,eAAS;AACT,WAAK,QAAQ;AACb;AAAA,IACF;AACA,QAAI,MAAM,EAAG;AAAA,EACf;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,WAAW,KAAK,QAAQ;AAC9C,QAAI,MAAM,EAAG;AACb,UAAM,SAAS,MAAM,QAAQ;AAAA,MAC3B;AAAA,MACA;AAAA,QACE,WAAW,MAAM;AACf,cAAI,MAAM,EAAG;AACb,gBAAM,IAAI,EAAE,QAAQ,UAAU,CAAC;AAC/B,UAAAA,QAAO;AAAA,QACT;AAAA,QACA,WAAW,MAAM;AACf,cAAI,MAAM,EAAG;AACb,gBAAM,IAAI,EAAE,QAAQ,UAAU,CAAC;AAC/B,UAAAA,QAAO;AAAA,QACT;AAAA,QACA,SAAS,MAAM;AACb,cAAI,MAAM,EAAG;AACb,gBAAM,IAAI,EAAE,QAAQ,OAAO,CAAC;AAC5B,mBAAS;AACT,eAAK,QAAQ;AAAA,QACf;AAAA,QACA,SAAS,CAAC,YAAoB;AAC5B,cAAI,MAAM,EAAG;AACb,gBAAM,IAAI,EAAE,QAAQ,SAAS,OAAO,QAAQ,CAAC;AAC7C,mBAAS;AACT,eAAK,QAAQ;AAAA,QACf;AAAA,MACF;AAAA,MACA;AAAA,IACF;AAEA,QAAI,MAAM,GAAG;AAEX,UAAI;AACF,cAAM,OAAO,KAAK;AAAA,MACpB,QAAQ;AAAA,MAER;AACA;AAAA,IACF;AACA,aAAS;AAAA,EACX,SAAS,KAAK;AACZ,QAAI,MAAM,EAAG;AACb,UAAM,IAAI;AAAA,MACR,QAAQ;AAAA,MACR,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,IAC9C,CAAC;AACD,aAAS;AACT,SAAK,QAAQ;AAAA,EACf;AACF;AAGA,eAAe,UAAyB;AACtC,QAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ;AACpD,MAAI,MAAM;AACR,UAAM,UAAU,KAAK,EAAE;AAAA,EACzB,OAAO;AACL,gBAAY;AACZ,oBAAgB,eAAe;AAC/B,IAAAA,QAAO;AAAA,EACT;AACF;AAOA,SAAS,uBAA6B;AACpC,cAAY;AACZ,MAAI,QAAQ;AACV,UAAM,OAAO;AACb,aAAS;AACT,QAAI;AACF,WAAK,KAAK,KAAK;AAAA,IACjB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,UAAQ,CAAC;AACT,cAAY;AACZ,EAAAA,QAAO;AACT;AAOO,SAAS,gBAAgB,SAAyC;AAGvE,cAAY,EAAE,WAAW;AAGzB,mBAAiB;AACjB,QAAM,KAAK,OAAO;AAClB,QAAM,OAAqB;AAAA,IACzB,GAAG;AAAA,IACH;AAAA,IACA,QAAQ;AAAA,IACR,cAAc,KAAK,IAAI;AAAA,EACzB;AACA,UAAQ,CAAC,GAAG,OAAO,IAAI;AACvB,EAAAA,QAAO;AACP,MAAI,CAAC,OAAO,GAAG;AACb,SAAK,UAAU,EAAE;AAAA,EACnB;AACA,SAAO,EAAE,GAAG;AACd;AAEA,eAAsB,gBAA+B;AACnD,MAAI,CAAC,UAAU,CAAC,UAAW;AAC3B,QAAM,OAAO,MAAM;AACnB,QAAM,WAAW,EAAE,QAAQ,SAAS,CAAC;AACrC,EAAAA,QAAO;AACT;AAEA,eAAsB,iBAAgC;AACpD,cAAY,EAAE,WAAW;AACzB,mBAAiB;AACjB,MAAI,CAAC,UAAU,CAAC,UAAW;AAC3B,QAAM,OAAO,OAAO;AACpB,QAAM,WAAW,EAAE,QAAQ,UAAU,CAAC;AACtC,EAAAA,QAAO;AACT;AAGA,eAAsB,eAA8B;AAClD,MAAI,UAAW,OAAM,WAAW,EAAE,QAAQ,OAAO,CAAC;AAClD,MAAI,QAAQ;AACV,UAAM,OAAO;AACb,aAAS;AACT,QAAI;AACF,YAAM,KAAK,KAAK;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,QAAM,QAAQ;AAChB;AAGA,eAAsB,iBAAiB,IAA2B;AAChE,cAAY,EAAE,WAAW;AACzB,mBAAiB;AACjB,QAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC1C,MAAI,CAAC,KAAM;AAEX,MAAI,KAAK,WAAW,UAAU,KAAK,WAAW,SAAS;AACrD,UAAM,IAAI,EAAE,QAAQ,UAAU,OAAO,OAAU,CAAC;AAAA,EAClD;AACA,MAAI,aAAa,cAAc,IAAI;AACjC,UAAM,WAAW,EAAE,QAAQ,OAAO,CAAC;AAAA,EACrC;AACA,QAAM,UAAU,EAAE;AACpB;AAGA,eAAsB,mBAAmB,IAA2B;AAClE,MAAI,OAAO,WAAW;AACpB,UAAM,aAAa;AACnB,YAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AACvC,IAAAA,QAAO;AACP;AAAA,EACF;AACA,UAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AACvC,EAAAA,QAAO;AACT;AAGA,eAAsB,gBAA+B;AACnD,cAAY;AACZ,MAAI,QAAQ;AACV,UAAM,OAAO;AACb,aAAS;AACT,QAAI;AACF,YAAM,KAAK,KAAK;AAAA,IAClB,QAAQ;AAAA,IAER;AAAA,EACF;AACA,UAAQ,CAAC;AACT,cAAY;AACZ,kBAAgB,eAAe;AAC/B,EAAAA,QAAO;AACT;AAEO,SAAS,gBAAgB,OAAqB;AACnD,SAAO,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG,KAAK,CAAC;AACxC,UAAQ,UAAU,IAAI;AACtB,EAAAA,QAAO;AACT;AAEO,SAAS,sBAAwC;AACtD,SAAO,SAAS;AAClB;AAGO,SAAS,wBAA8B;AAC5C,cAAY;AACZ,WAAS;AACT,UAAQ,CAAC;AACT,cAAY;AACZ,SAAO;AACP,eAAa,MAAM;AACnB,kBAAgB,eAAe;AACjC;AAEO,SAAS,kBAAkB,UAAgC;AAChE,EAAAD,WAAU,IAAI,QAAQ;AACtB,WAAS,SAAS,CAAC;AACnB,SAAO,MAAM;AACX,IAAAA,WAAU,OAAO,QAAQ;AAAA,EAC3B;AACF;;;AD9TO,SAAS,mBAAmB,WAAgD;AACjF,SAAO,aAAa,YAAY,EAAE,iBAAiB;AACrD;AAGO,SAAS,MAAM,SAAoC;AACxD,QAAM,WAAW,CAAC,MAAwC,YAAY,EAAE,kBAAkB,CAAC,KAAK;AAChG,MAAI,QAAQ,QAAQ;AAClB,UAAM,EAAE,IAAAE,IAAG,IAAI;AAAA,MACb,SAAS;AAAA,QACP,MAAM,QAAQ;AAAA,QACd,iBAAiB;AAAA,QACjB,OAAO,QAAQ;AAAA,QACf,UAAU;AAAA,QACV,SAAS,EAAE,QAAQ,QAAQ,OAAO;AAAA,MACpC,CAAC;AAAA,IACH;AACA,WAAO,EAAE,IAAAA,KAAI,QAAQ,UAAU;AAAA,EACjC;AACA,QAAM,SAAS,mBAAmB,QAAQ,MAAM;AAChD,QAAM,SAAS;AAAA,IACb,MAAM,QAAQ;AAAA,IACd,iBAAiB,QAAQ;AAAA,IACzB,OAAO,QAAQ;AAAA,IACf,sBAAsB,QAAQ;AAAA,EAChC;AACA,MAAI,WAAW,YAAY;AAEzB,UAAM,EAAE,IAAAA,IAAG,IAAI;AAAA,MACb,SAAS;AAAA,QACP,GAAG;AAAA,QACH,UAAU;AAAA,QACV,UAAU;AAAA,UACR,SAAS,QAAQ;AAAA,UACjB,UAAU,QAAQ;AAAA,UAClB,OAAO,QAAQ;AAAA,UACf,SAAS,QAAQ,WAAW;AAAA,QAC9B;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAO,EAAE,IAAAA,KAAI,OAAO;AAAA,EACtB;AACA,QAAM,EAAE,GAAG,IAAI,gBAAgB,SAAS,EAAE,GAAG,QAAQ,UAAU,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM,EAAE,CAAC,CAAC;AAC3G,SAAO,EAAE,IAAI,OAAO;AACtB;;;ADjEA;AACA;AACA;AACA;AAYA;","names":["el","listeners","source","listeners","notify","id"]}