@burdenoff/fe-libs 2026.903.3 → 2026.903.5

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.
Files changed (41) hide show
  1. package/dist/shared/assistant/index.d.ts +6 -0
  2. package/dist/shared/assistant/index.d.ts.map +1 -1
  3. package/dist/shared/assistant/speech/browserSpeechEngine.d.ts +3 -0
  4. package/dist/shared/assistant/speech/browserSpeechEngine.d.ts.map +1 -0
  5. package/dist/shared/assistant/speech/browserSpeechEngine.js +89 -0
  6. package/dist/shared/assistant/speech/createSpeechEngine.d.ts +50 -0
  7. package/dist/shared/assistant/speech/createSpeechEngine.d.ts.map +1 -0
  8. package/dist/shared/assistant/speech/createSpeechEngine.js +31 -0
  9. package/dist/shared/assistant/speech/hostedTtsProtocol.d.ts +118 -0
  10. package/dist/shared/assistant/speech/hostedTtsProtocol.d.ts.map +1 -0
  11. package/dist/shared/assistant/speech/hostedTtsProtocol.js +42 -0
  12. package/dist/shared/assistant/speech/hostedTtsSpeechEngine.d.ts +10 -0
  13. package/dist/shared/assistant/speech/hostedTtsSpeechEngine.d.ts.map +1 -0
  14. package/dist/shared/assistant/speech/hostedTtsSpeechEngine.js +121 -0
  15. package/dist/shared/assistant/speech/hostedVoiceCatalogue.d.ts +35 -0
  16. package/dist/shared/assistant/speech/hostedVoiceCatalogue.d.ts.map +1 -0
  17. package/dist/shared/assistant/speech/hostedVoiceCatalogue.js +53 -0
  18. package/dist/shared/assistant/speech/index.d.ts +8 -0
  19. package/dist/shared/assistant/speech/index.d.ts.map +1 -0
  20. package/dist/shared/assistant/speech/routingSpeechEngine.d.ts +8 -0
  21. package/dist/shared/assistant/speech/routingSpeechEngine.d.ts.map +1 -0
  22. package/dist/shared/assistant/speech/routingSpeechEngine.js +1 -0
  23. package/dist/shared/assistant/speech/types.d.ts +65 -0
  24. package/dist/shared/assistant/speech/types.d.ts.map +1 -0
  25. package/dist/shared/assistant/voice/assistantPollSchedule.d.ts +38 -0
  26. package/dist/shared/assistant/voice/assistantPollSchedule.d.ts.map +1 -0
  27. package/dist/shared/assistant/voice/languagePair.d.ts +36 -0
  28. package/dist/shared/assistant/voice/languagePair.d.ts.map +1 -0
  29. package/dist/shared/assistant/voice/languagePair.js +15 -0
  30. package/dist/shared/assistant/voice/languages.d.ts +74 -0
  31. package/dist/shared/assistant/voice/languages.d.ts.map +1 -0
  32. package/dist/shared/assistant/voice/languages.js +124 -0
  33. package/dist/shared/assistant/voice/speakableText.d.ts +121 -0
  34. package/dist/shared/assistant/voice/speakableText.d.ts.map +1 -0
  35. package/dist/shared/assistant/voice/voicePreview.d.ts +33 -0
  36. package/dist/shared/assistant/voice/voicePreview.d.ts.map +1 -0
  37. package/dist/shared/assistant/voice/voiceTranscript.d.ts +49 -0
  38. package/dist/shared/assistant/voice/voiceTranscript.d.ts.map +1 -0
  39. package/dist/shared/assistant/voice/voiceTranscript.js +42 -0
  40. package/dist/shared-assistant.js +5 -1
  41. package/package.json +1 -1
@@ -19,4 +19,10 @@ export type { UseVoiceInputOptions, UseVoiceInputResult, VoiceInputError } from
19
19
  export { toCaptionText } from './voice/captionText';
20
20
  export { parseConfirmationRefusal, findPendingWrite } from './pendingWrite';
21
21
  export type { PendingWrite, PendingWriteValue } from './pendingWrite';
22
+ export { createSpeechEngines } from './speech/createSpeechEngine';
23
+ export type { SpeechEngine, SpeechSpeakOptions, SpeechSpeakHandlers, SpeechErrorReason } from './speech/types';
24
+ export { buildVoiceTranscriptMarkdown, voiceTranscriptFilename, buildContinuedFromVoiceNote } from './voice/voiceTranscript';
25
+ export { resolveLanguagePair, replyFollowsInput } from './voice/languagePair';
26
+ export { ASSISTANT_LANGUAGES, resolveAssistantLanguage, findAssistantLanguage } from './voice/languages';
27
+ export type { AssistantLanguage } from './voice/languages';
22
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,OAAO,CAAC;AAEf,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AACpF,OAAO,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAKpB,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,4BAA4B,EAC5B,sBAAsB,EACtB,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,qBAAqB,EACrB,yBAAyB,EACzB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,OAAO,CAAC;AAEf,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,iBAAiB,EACjB,2BAA2B,EAC3B,oBAAoB,EACpB,2BAA2B,EAC3B,YAAY,GACb,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC/G,OAAO,EAAE,4BAA4B,EAAE,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAC7H,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AACzG,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { SpeechEngine } from './types';
2
+ export declare const browserSpeechEngine: SpeechEngine;
3
+ //# sourceMappingURL=browserSpeechEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browserSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/browserSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,YAAY,EAKb,MAAM,SAAS,CAAC;AAsCjB,eAAO,MAAM,mBAAmB,EAAE,YAsGjC,CAAC"}
@@ -0,0 +1,89 @@
1
+ //#region src/shared/assistant/speech/browserSpeechEngine.ts
2
+ function e() {
3
+ if (typeof window > "u") return null;
4
+ try {
5
+ return window.speechSynthesis ?? null;
6
+ } catch {
7
+ return null;
8
+ }
9
+ }
10
+ function t() {
11
+ return typeof window < "u" && typeof window.SpeechSynthesisUtterance == "function";
12
+ }
13
+ function n(e) {
14
+ switch (e) {
15
+ case "canceled":
16
+ case "cancelled":
17
+ case "interrupted": return "cancelled";
18
+ case "not-allowed": return "not-allowed";
19
+ case "synthesis-unavailable":
20
+ case "language-unavailable":
21
+ case "voice-unavailable": return "unsupported";
22
+ default: return "unknown";
23
+ }
24
+ }
25
+ var r = 0, i = {
26
+ id: "browser-speech-synthesis",
27
+ isSupported() {
28
+ return e() !== null && t();
29
+ },
30
+ listVoices() {
31
+ try {
32
+ return e()?.getVoices() ?? [];
33
+ } catch {
34
+ return [];
35
+ }
36
+ },
37
+ subscribeVoices(t) {
38
+ let n = e();
39
+ if (!n?.addEventListener) return () => {};
40
+ let r = () => t();
41
+ return n.addEventListener("voiceschanged", r), () => n.removeEventListener("voiceschanged", r);
42
+ },
43
+ speak(i, a, o = {}) {
44
+ let s = e();
45
+ if (!s || !t()) {
46
+ o.onError?.("unsupported");
47
+ return;
48
+ }
49
+ let c = i.filter((e) => e.trim().length > 0);
50
+ if (c.length === 0) {
51
+ o.onEnd?.();
52
+ return;
53
+ }
54
+ r += 1;
55
+ let l = r;
56
+ try {
57
+ s.cancel();
58
+ } catch {}
59
+ c.forEach((e, t) => {
60
+ let i = new window.SpeechSynthesisUtterance(e);
61
+ i.lang = a.lang, a.voice && (i.voice = a.voice), a.rate !== void 0 && (i.rate = a.rate), i.onstart = () => {
62
+ l === r && o.onChunkStart?.(t);
63
+ }, i.onerror = (e) => {
64
+ if (l === r) {
65
+ r += 1;
66
+ try {
67
+ s.cancel();
68
+ } catch {}
69
+ o.onError?.(n(e.error));
70
+ }
71
+ }, t === c.length - 1 && (i.onend = () => {
72
+ l === r && o.onEnd?.();
73
+ });
74
+ try {
75
+ s.speak(i);
76
+ } catch {
77
+ l === r && o.onError?.("unknown");
78
+ }
79
+ });
80
+ },
81
+ cancel() {
82
+ r += 1;
83
+ try {
84
+ e()?.cancel();
85
+ } catch {}
86
+ }
87
+ };
88
+ //#endregion
89
+ export { i as browserSpeechEngine };
@@ -0,0 +1,50 @@
1
+ import { SpeechEngine } from './types';
2
+ export interface SpeechEngineConfig {
3
+ /** Our proxy's URL. Empty/undefined keeps the device engine. */
4
+ endpoint?: string | null;
5
+ getHeaders?: () => Record<string, string>;
6
+ fetchImpl?: typeof fetch;
7
+ }
8
+ /**
9
+ * Point the assistant at a hosted TTS proxy.
10
+ *
11
+ * Called by the host shell's provider, exactly as `configureGeocoder` is. This
12
+ * is the supported way to turn hosted speech on; the environment variable below
13
+ * only ever works when this package is run directly (dev / standalone).
14
+ *
15
+ * ★ `getHeaders` is not optional in practice, only in the type. The proxy turns
16
+ * text into audio the org is BILLED for, so an endpoint that accepts anonymous
17
+ * POSTs is a metered API open to anyone who reads the bundle. The engine has
18
+ * always supported per-request headers; until now nothing could reach that hook
19
+ * from the host, which meant the only configuration this function could express
20
+ * was an unauthenticated one. It is a function rather than a value because
21
+ * access tokens expire and the engine outlives any one of them — reading a
22
+ * token once at configure time would work until it rotated and then fail
23
+ * exactly where it is hardest to notice, mid-reply.
24
+ */
25
+ export declare function configureSpeechEngine(options: {
26
+ ttsEndpoint?: string | null;
27
+ getHeaders?: () => Record<string, string>;
28
+ }): void;
29
+ /**
30
+ * Read the rollout config.
31
+ *
32
+ * Order matters: explicit runtime configuration, then a global the host can set
33
+ * before the bundle loads, and only then the build-time variable — which is
34
+ * eliminated in the published package and therefore useful for local dev only.
35
+ */
36
+ export declare function readSpeechEngineConfig(): SpeechEngineConfig;
37
+ /**
38
+ * The engine to use, or the device engine when hosted TTS is not configured.
39
+ *
40
+ * Returns `null` for `hosted` when off, so callers can tell "hosted is not
41
+ * configured" from "hosted is configured but cannot speak this language".
42
+ */
43
+ export declare function createSpeechEngines(config?: SpeechEngineConfig): {
44
+ device: SpeechEngine;
45
+ hosted: SpeechEngine | null;
46
+ };
47
+ export declare function getSpeechEngine(): SpeechEngine;
48
+ /** Test seam: drop the singleton so the next call re-reads the config. */
49
+ export declare function resetSpeechEngine(): void;
50
+ //# sourceMappingURL=createSpeechEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/createSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,kBAAkB;IACjC,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE;IAC7C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C,GAAG,IAAI,CAmBP;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,kBAAkB,CAY3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,kBAA6C,GAAG;IAC1F,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B,CASA;AAeD,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
@@ -0,0 +1,31 @@
1
+ import { browserSpeechEngine as e } from "./browserSpeechEngine.js";
2
+ import { createHostedTtsEngine as t } from "./hostedTtsSpeechEngine.js";
3
+ import "./routingSpeechEngine.js";
4
+ //#region src/shared/assistant/speech/createSpeechEngine.ts
5
+ var n, r;
6
+ function i() {
7
+ if (n) return {
8
+ endpoint: n,
9
+ getHeaders: r
10
+ };
11
+ if (typeof window < "u") {
12
+ let e = window.__HB_TTS_ENDPOINT__;
13
+ if (e) return {
14
+ endpoint: e,
15
+ getHeaders: r
16
+ };
17
+ }
18
+ return { endpoint: (void 0)?.trim() || null };
19
+ }
20
+ function a(n = i()) {
21
+ return {
22
+ device: e,
23
+ hosted: n.endpoint ? t({
24
+ endpoint: n.endpoint,
25
+ getHeaders: n.getHeaders,
26
+ fetchImpl: n.fetchImpl
27
+ }) : null
28
+ };
29
+ }
30
+ //#endregion
31
+ export { a as createSpeechEngines };
@@ -0,0 +1,118 @@
1
+ import { SpeechErrorReason } from './types';
2
+ /** What the client sends per chunk. */
3
+ export interface HostedTtsRequest {
4
+ text: string;
5
+ /** BCP-47, e.g. `ta-IN`. The vendor needs the region, not a bare subtag. */
6
+ languageCode: string;
7
+ /** Vendor voice id, e.g. `ta-IN-Chirp3-HD-Kore`. Omitted = server default. */
8
+ voiceName?: string;
9
+ /** Vendor voice tier, so the backend need not infer it from the name. */
10
+ tier: string;
11
+ /**
12
+ * The same knob the device engine calls `rate`.
13
+ *
14
+ * ★ The accepted range depends on which vendor ENDPOINT the proxy calls, not
15
+ * on the voice tier — see `SPEAKING_RATE_MAX_SYNC` below. Values in use are
16
+ * 0.9, and 0.85 for Indic scripts and Arabic, comfortably inside every
17
+ * variant, so this has never affected playback.
18
+ */
19
+ speakingRate?: number;
20
+ }
21
+ /**
22
+ * Google's synthesize response is `{ audioContent: <base64 mp3> }`. We accept
23
+ * that shape directly so the proxy can be a thin pass-through, but the proxy is
24
+ * also free to return raw audio bytes — see `decodeAudioResponse`.
25
+ */
26
+ export interface HostedTtsJsonResponse {
27
+ audioContent?: string;
28
+ error?: {
29
+ message?: string;
30
+ };
31
+ }
32
+ /**
33
+ * The accepted speaking-rate bounds, for any caller that clamps.
34
+ *
35
+ * ★ The ceiling is a property of the ENDPOINT, not of the voice tier, and the
36
+ * vendor's own rejection message says both numbers out loud:
37
+ *
38
+ * "Unable to adjust speaking rate. Please ensure that speaking_rate is in the
39
+ * range [0.25, 4.0] for sync synthesis and in the range [0.25, 2.0] for
40
+ * streaming synthesis."
41
+ *
42
+ * Measured against the live API on 2026-08-31 with `en-IN-Chirp3-HD-Puck` and a
43
+ * fixed sentence, reading the returned LINEAR16 duration rather than trusting
44
+ * the HTTP status — 4.0 synthesises and is genuinely ~4x faster (21.93s at 0.25
45
+ * down to 1.37s at 4.0); 4.1, 5.0 and 0.2 are all rejected with the message
46
+ * above.
47
+ *
48
+ * This corrects a previous edit of this file which narrowed the ceiling to 2.0
49
+ * and attributed 4.0 to "the older Standard/WaveNet tiers". That reading was
50
+ * wrong: 2.0 is the STREAMING limit, and the tier has nothing to do with it.
51
+ *
52
+ * `SPEAKING_RATE_MAX` is the sync ceiling because the proxy calls
53
+ * `v1/text:synthesize`. Moving it to `v1/text:streamingSynthesize` — tempting,
54
+ * since that is how you cut time-to-first-audio — halves the ceiling, and rates
55
+ * the client had treated as valid would start returning 400. That switch has to
56
+ * change this alias in the same commit, which is why both bounds are named
57
+ * rather than one being a magic number.
58
+ */
59
+ export declare const SPEAKING_RATE_MIN = 0.25;
60
+ export declare const SPEAKING_RATE_MAX_SYNC = 4;
61
+ export declare const SPEAKING_RATE_MAX_STREAMING = 2;
62
+ export declare const SPEAKING_RATE_MAX = 4;
63
+ /**
64
+ * Should a failure stop the engine claiming support for the rest of the page?
65
+ *
66
+ * ★ `not-allowed` has TWO producers and only one of them is terminal:
67
+ *
68
+ * - `classifyHttpFailure` maps HTTP 401/403 to it — the proxy refuses us, and
69
+ * every later request will be refused identically. Latching is right: it
70
+ * makes `routingSpeechEngine` fall back to device voices instead of
71
+ * repeating a request that cannot succeed.
72
+ * - the engine maps the browser's `NotAllowedError` to it — the autoplay
73
+ * policy declining to play audio that was not started by a user gesture.
74
+ * That is PER-GESTURE and entirely transient; the very next tap works.
75
+ *
76
+ * Latching on the second silently deleted the hosted voice for the whole page
77
+ * after a single refusal — and auto-speak is precisely the path that triggers
78
+ * it, because an arriving reply is not a gesture. The user would hear Chirp 3
79
+ * once, be refused once, and get device voices for the rest of the session with
80
+ * nothing explaining why.
81
+ *
82
+ * Pure and exported because the engine itself cannot be unit-tested here (no
83
+ * DOM, no Audio), so the decision is kept where it CAN be.
84
+ */
85
+ export declare function shouldDisableEngine(reason: string | undefined, transient: boolean): boolean;
86
+ /** Vendor limit is 5000 bytes per request; stay well inside it. */
87
+ export declare const HOSTED_TTS_MAX_CHARS = 2000;
88
+ export declare function buildRequest(text: string, languageCode: string, tier: string, voiceName?: string | null, speakingRate?: number): HostedTtsRequest;
89
+ /**
90
+ * Map a transport failure onto the reasons the UI already knows how to explain.
91
+ *
92
+ * `cancelled` is deliberately NOT produced here — an aborted request is the
93
+ * caller's own doing and the engine reports it, exactly as the browser engine
94
+ * swallows its own `canceled` utterance errors.
95
+ */
96
+ export declare function classifyHttpFailure(status: number): SpeechErrorReason;
97
+ /** Base64 → bytes, without assuming a DOM (`atob` is not in node). */
98
+ export declare function base64ToBytes(base64: string): Uint8Array;
99
+ /**
100
+ * Accept either shape the proxy may return: a JSON body carrying base64
101
+ * (the vendor's own shape, so the proxy can pass it straight through) or raw
102
+ * audio bytes.
103
+ *
104
+ * Returns null when the payload carries no audio at all — which must be treated
105
+ * as a failure, not as silence. A zero-byte "success" that plays nothing is the
106
+ * dead-button bug this feature already fixed once.
107
+ */
108
+ export declare function decodeAudioResponse(contentType: string | null, body: ArrayBuffer | HostedTtsJsonResponse): Uint8Array | null;
109
+ /**
110
+ * Split text the hosted vendor would reject for length.
111
+ *
112
+ * `chunkForSpeech` already splits for the browser engine's ~15s utterance
113
+ * ceiling, and those chunks are normally far under the vendor limit — this is a
114
+ * backstop for a single pathological sentence, and it splits on whitespace so a
115
+ * word is never cut in half.
116
+ */
117
+ export declare function splitForVendor(text: string, limit?: number): string[];
118
+ //# sourceMappingURL=hostedTtsProtocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostedTtsProtocol.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/hostedTtsProtocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,sBAAsB,IAAM,CAAC;AAC1C,eAAO,MAAM,2BAA2B,IAAM,CAAC;AAC/C,eAAO,MAAM,iBAAiB,IAAyB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAG3F;AAED,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EACzB,YAAY,CAAC,EAAE,MAAM,GACpB,gBAAgB,CAUlB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAOrE;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAaxD;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,IAAI,EAAE,WAAW,GAAG,qBAAqB,GACxC,UAAU,GAAG,IAAI,CAWnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,SAAuB,GAAG,MAAM,EAAE,CAenF"}
@@ -0,0 +1,42 @@
1
+ //#region src/shared/assistant/speech/hostedTtsProtocol.ts
2
+ function e(e, t) {
3
+ return t ? !1 : e === "not-allowed" || e === "unsupported";
4
+ }
5
+ var t = 2e3;
6
+ function n(e, t, n, r, i) {
7
+ let a = {
8
+ text: e,
9
+ languageCode: t,
10
+ tier: n
11
+ };
12
+ return r && r.toLowerCase().startsWith(t.toLowerCase()) && (a.voiceName = r), i !== void 0 && (a.speakingRate = i), a;
13
+ }
14
+ function r(e) {
15
+ return e === 401 || e === 403 ? "not-allowed" : e === 404 || e === 501 ? "unsupported" : "unknown";
16
+ }
17
+ function i(e) {
18
+ let t = globalThis.Buffer, n = typeof atob == "function" ? atob(e) : t?.from(e, "base64").toString("binary") ?? "", r = new Uint8Array(n.length);
19
+ for (let e = 0; e < n.length; e += 1) r[e] = n.charCodeAt(e);
20
+ return r;
21
+ }
22
+ function a(e, t) {
23
+ if (t instanceof ArrayBuffer) return t.byteLength > 0 ? new Uint8Array(t) : null;
24
+ if (e?.includes("json") || typeof t == "object") {
25
+ let e = t.audioContent;
26
+ if (!e) return null;
27
+ let n = i(e);
28
+ return n.length > 0 ? n : null;
29
+ }
30
+ return null;
31
+ }
32
+ function o(e, n = t) {
33
+ if (e.length <= n) return [e];
34
+ let r = [], i = e;
35
+ for (; i.length > n;) {
36
+ let e = i.slice(0, n).lastIndexOf(" "), t = e > n * .5 ? e : n;
37
+ r.push(i.slice(0, t).trim()), i = i.slice(t).trim();
38
+ }
39
+ return i && r.push(i), r.filter((e) => e.length > 0);
40
+ }
41
+ //#endregion
42
+ export { n as buildRequest, r as classifyHttpFailure, a as decodeAudioResponse, e as shouldDisableEngine, o as splitForVendor };
@@ -0,0 +1,10 @@
1
+ import { SpeechEngine } from './types';
2
+ export interface HostedTtsOptions {
3
+ /** Absolute or same-origin path of OUR proxy. Never the vendor directly. */
4
+ endpoint: string;
5
+ /** Extra headers per request (auth). Called fresh each time so a rotated token is picked up. */
6
+ getHeaders?: () => Record<string, string>;
7
+ fetchImpl?: typeof fetch;
8
+ }
9
+ export declare function createHostedTtsEngine(options: HostedTtsOptions): SpeechEngine;
10
+ //# sourceMappingURL=hostedTtsSpeechEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostedTtsSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/hostedTtsSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH,OAAO,KAAK,EAAE,YAAY,EAA4D,MAAM,SAAS,CAAC;AAEtG,MAAM,WAAW,gBAAgB;IAC/B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAkP7E"}
@@ -0,0 +1,121 @@
1
+ import { HOSTED_TTS_TIER as e, HOSTED_VOICES as t, hostedLocaleFor as n } from "./hostedVoiceCatalogue.js";
2
+ import { buildRequest as r, classifyHttpFailure as i, decodeAudioResponse as a, shouldDisableEngine as o, splitForVendor as s } from "./hostedTtsProtocol.js";
3
+ //#region src/shared/assistant/speech/hostedTtsSpeechEngine.ts
4
+ function c(c) {
5
+ let l = c.fetchImpl ?? ((...e) => fetch(...e)), u = null, d = null, f = 0, p = null, m = !1, h = () => {
6
+ d &&= (URL.revokeObjectURL(d), null);
7
+ }, g = () => {
8
+ if (u) {
9
+ u.onended = null, u.onerror = null;
10
+ try {
11
+ u.pause();
12
+ } catch {}
13
+ u = null;
14
+ }
15
+ h();
16
+ };
17
+ async function _(t, o, s) {
18
+ let u = r(t, n(o.lang) ?? o.lang, e, o.voice?.name ?? null, o.rate), d = await l(c.endpoint, {
19
+ method: "POST",
20
+ signal: s,
21
+ headers: {
22
+ "content-type": "application/json",
23
+ ...c.getHeaders?.() ?? {}
24
+ },
25
+ body: JSON.stringify(u)
26
+ });
27
+ if (!d.ok) {
28
+ let e = /* @__PURE__ */ Error(`hosted tts ${d.status}`);
29
+ throw e.reason = i(d.status), e;
30
+ }
31
+ let f = d.headers.get("content-type"), p = a(f, f?.includes("json") ? await d.json() : await d.arrayBuffer());
32
+ if (!p) {
33
+ let e = /* @__PURE__ */ Error("hosted tts returned no audio");
34
+ throw e.reason = "unknown", e;
35
+ }
36
+ return p;
37
+ }
38
+ function v(e, t, n) {
39
+ return new Promise((r, i) => {
40
+ if (t !== f) {
41
+ r();
42
+ return;
43
+ }
44
+ let a = new Blob([e.slice().buffer], { type: "audio/mpeg" });
45
+ h(), d = URL.createObjectURL(a);
46
+ let o = new Audio(d);
47
+ u = o, o.onended = () => {
48
+ t === f && r();
49
+ }, o.onerror = () => {
50
+ if (t !== f) {
51
+ r();
52
+ return;
53
+ }
54
+ let e = /* @__PURE__ */ Error("audio playback failed");
55
+ e.reason = "unknown", i(e);
56
+ }, o.play().then(() => {
57
+ t === f && n?.();
58
+ }, (e) => {
59
+ if (t !== f) {
60
+ r();
61
+ return;
62
+ }
63
+ let n = /* @__PURE__ */ Error("audio play rejected"), a = e?.name === "NotAllowedError";
64
+ n.reason = a ? "not-allowed" : "unknown", n.transient = a, i(n);
65
+ });
66
+ });
67
+ }
68
+ return {
69
+ id: "hosted-tts",
70
+ isSupported() {
71
+ return m ? !1 : typeof window < "u" && typeof window.Audio == "function" && typeof fetch == "function";
72
+ },
73
+ listVoices() {
74
+ return [...t];
75
+ },
76
+ subscribeVoices() {
77
+ return () => {};
78
+ },
79
+ speak(e, t, n = {}) {
80
+ let r = e.filter((e) => e.trim().length > 0).flatMap((e) => s(e));
81
+ if (r.length === 0) {
82
+ n.onEnd?.();
83
+ return;
84
+ }
85
+ f += 1;
86
+ let i = f;
87
+ g();
88
+ let a = new AbortController();
89
+ p = a;
90
+ let c = !1, l = () => {
91
+ c || (c = !0, n.onStart?.());
92
+ }, u = (e) => {
93
+ let n = _(e, t, a.signal);
94
+ return n.catch(() => {}), n;
95
+ };
96
+ (async () => {
97
+ try {
98
+ let e = u(r[0]);
99
+ for (let t = 0; t < r.length; t += 1) {
100
+ if (i !== f) return;
101
+ let a = await e;
102
+ if (i !== f) return;
103
+ let o = r[t + 1];
104
+ o !== void 0 && (e = u(o)), n.onChunkStart?.(t), await v(a, i, l);
105
+ }
106
+ i === f && n.onEnd?.();
107
+ } catch (e) {
108
+ if (a.abort(), i !== f) return;
109
+ f += 1, g();
110
+ let t = e?.reason;
111
+ o(t, e?.transient === !0) && (m = !0), n.onError?.(t === "not-allowed" || t === "unsupported" || t === "cancelled" ? t : "unknown");
112
+ }
113
+ })();
114
+ },
115
+ cancel() {
116
+ f += 1, p?.abort(), p = null, g();
117
+ }
118
+ };
119
+ }
120
+ //#endregion
121
+ export { c as createHostedTtsEngine };
@@ -0,0 +1,35 @@
1
+ import { SpeechVoiceLike } from '../voice/speechVoices';
2
+ /** Vendor tier this catalogue describes; sent to the backend with each request. */
3
+ export declare const HOSTED_TTS_TIER = "chirp3-hd";
4
+ /**
5
+ * Every hosted voice, as `SpeechVoiceLike` so it is interchangeable with a
6
+ * device voice everywhere downstream.
7
+ *
8
+ * The vendor id form is `<vendor-locale>-Chirp3-HD-<Speaker>`, e.g.
9
+ * `ta-IN-Chirp3-HD-Kore`. It is used as BOTH the display name and the id the
10
+ * backend receives, because the persisted preference is keyed by name — a
11
+ * separate display label would break a saved pick the first time it changed.
12
+ *
13
+ * `name` is therefore always the VENDOR's spelling (it is sent on the wire and
14
+ * must exist), while `lang` is always the APP's (it is matched against
15
+ * `SPEECH_LANG`). For every locale but Mandarin these are the same string.
16
+ */
17
+ export declare const HOSTED_VOICES: readonly SpeechVoiceLike[];
18
+ /**
19
+ * The exact vendor locale to request for an app tag, or null when the vendor
20
+ * cannot speak the language at all.
21
+ *
22
+ * Prefers an exact match (`en-GB` stays `en-GB`, `zh-CN` becomes `cmn-CN`) and
23
+ * otherwise falls back to the primary subtag's canonical vendor locale
24
+ * (`ar-SA` -> `ar-XA`, `zh` -> `cmn-CN`).
25
+ */
26
+ export declare function hostedLocaleFor(tag: string): string | null;
27
+ /**
28
+ * True when the hosted engine can actually speak this language.
29
+ *
30
+ * The caller needs this to decide whether to fall back to the device engine
31
+ * for a reply in a language the vendor does not cover, rather than sending a
32
+ * request that can only fail.
33
+ */
34
+ export declare function hostedSupportsLanguage(tag: string): boolean;
35
+ //# sourceMappingURL=hostedVoiceCatalogue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostedVoiceCatalogue.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/hostedVoiceCatalogue.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,mFAAmF;AACnF,eAAO,MAAM,eAAe,cAAc,CAAC;AAyE3C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,eAAe,EAQnD,CAAC;AAiCF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO1D;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE3D"}
@@ -0,0 +1,53 @@
1
+ //#region src/shared/assistant/speech/hostedVoiceCatalogue.ts
2
+ var e = "chirp3-hd", t = [
3
+ "Achernar",
4
+ "Algenib",
5
+ "Charon",
6
+ "Kore",
7
+ "Puck",
8
+ "Zephyr"
9
+ ], n = [
10
+ "en-US",
11
+ "en-GB",
12
+ "en-IN",
13
+ "ta-IN",
14
+ "hi-IN",
15
+ "bn-IN",
16
+ "te-IN",
17
+ "mr-IN",
18
+ "gu-IN",
19
+ "kn-IN",
20
+ "ml-IN",
21
+ "pa-IN",
22
+ "ur-IN",
23
+ "ar-XA",
24
+ "es-ES",
25
+ "cmn-CN",
26
+ "ja-JP",
27
+ "fr-FR",
28
+ "de-DE"
29
+ ], r = { "cmn-CN": "zh-CN" };
30
+ function i(e) {
31
+ return r[e] ?? e;
32
+ }
33
+ var a = n.flatMap((e) => t.map((t) => ({
34
+ name: `${e}-Chirp3-HD-${t}`,
35
+ lang: i(e),
36
+ localService: !1
37
+ }))), o = /* @__PURE__ */ new Map();
38
+ for (let e of n) for (let t of new Set([i(e), e])) {
39
+ let n = t.toLowerCase().split("-")[0];
40
+ o.has(n) || o.set(n, e);
41
+ }
42
+ var s = /* @__PURE__ */ new Map();
43
+ for (let e of n) s.set(e.toLowerCase(), e), s.set(i(e).toLowerCase(), e);
44
+ function c(e) {
45
+ let t = e.trim().replace("_", "-");
46
+ if (!t) return null;
47
+ let n = s.get(t.toLowerCase());
48
+ if (n) return n;
49
+ let r = t.toLowerCase().split("-")[0];
50
+ return o.get(r) ?? null;
51
+ }
52
+ //#endregion
53
+ export { e as HOSTED_TTS_TIER, a as HOSTED_VOICES, c as hostedLocaleFor };
@@ -0,0 +1,8 @@
1
+ export { browserSpeechEngine } from './browserSpeechEngine';
2
+ export { createHostedTtsEngine } from './hostedTtsSpeechEngine';
3
+ export { configureSpeechEngine, createSpeechEngines, getSpeechEngine, readSpeechEngineConfig, resetSpeechEngine, } from './createSpeechEngine';
4
+ export { createRoutingSpeechEngine } from './routingSpeechEngine';
5
+ export type { SpeechEngineConfig } from './createSpeechEngine';
6
+ export { HOSTED_VOICES, HOSTED_TTS_TIER, hostedSupportsLanguage } from './hostedVoiceCatalogue';
7
+ export type { SpeechEngine, SpeechErrorReason, SpeechSpeakHandlers, SpeechSpeakOptions, SpeechVoiceLike, } from './types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChG,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { SpeechEngine } from './types';
2
+ export interface RoutingEngines {
3
+ device: SpeechEngine;
4
+ /** Null when hosted TTS is not configured — then this is a pass-through. */
5
+ hosted: SpeechEngine | null;
6
+ }
7
+ export declare function createRoutingSpeechEngine({ device, hosted }: RoutingEngines): SpeechEngine;
8
+ //# sourceMappingURL=routingSpeechEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routingSpeechEngine.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/speech/routingSpeechEngine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAA4D,MAAM,SAAS,CAAC;AAEtG,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,4EAA4E;IAC5E,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,yBAAyB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,YAAY,CAoG1F"}
@@ -0,0 +1 @@
1
+ import "./hostedVoiceCatalogue.js";