@leiboss/dsh-tts 0.0.0-stage → 0.3.0
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.
- package/CHANGELOG.md +111 -0
- package/LICENSE +21 -0
- package/README.md +374 -2
- package/cordis.patch.yml +4 -0
- package/dist/audio/mp3-duration.d.ts +23 -0
- package/dist/audio/mp3-duration.d.ts.map +1 -0
- package/dist/audio/mp3-duration.js +140 -0
- package/dist/audio/mp3-duration.js.map +1 -0
- package/dist/audio-route.d.ts +122 -0
- package/dist/audio-route.d.ts.map +1 -0
- package/dist/audio-route.js +408 -0
- package/dist/audio-route.js.map +1 -0
- package/dist/cache.d.ts +50 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +105 -0
- package/dist/cache.js.map +1 -0
- package/dist/chunked-tts.d.ts +34 -0
- package/dist/chunked-tts.d.ts.map +1 -0
- package/dist/chunked-tts.js +121 -0
- package/dist/chunked-tts.js.map +1 -0
- package/dist/chunker.d.ts +19 -0
- package/dist/chunker.d.ts.map +1 -0
- package/dist/chunker.js +98 -0
- package/dist/chunker.js.map +1 -0
- package/dist/client.js +90 -0
- package/dist/concurrency.d.ts +33 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +95 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config.d.ts +174 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +186 -0
- package/dist/config.js.map +1 -0
- package/dist/edge-tts.d.ts +74 -0
- package/dist/edge-tts.d.ts.map +1 -0
- package/dist/edge-tts.js +161 -0
- package/dist/edge-tts.js.map +1 -0
- package/dist/engine-health.d.ts +108 -0
- package/dist/engine-health.d.ts.map +1 -0
- package/dist/engine-health.js +170 -0
- package/dist/engine-health.js.map +1 -0
- package/dist/engines/edge-js.d.ts +51 -0
- package/dist/engines/edge-js.d.ts.map +1 -0
- package/dist/engines/edge-js.js +98 -0
- package/dist/engines/edge-js.js.map +1 -0
- package/dist/errors.d.ts +66 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +110 -0
- package/dist/errors.js.map +1 -0
- package/dist/fallback.d.ts +69 -0
- package/dist/fallback.d.ts.map +1 -0
- package/dist/fallback.js +89 -0
- package/dist/fallback.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +723 -0
- package/dist/index.js.map +1 -0
- package/dist/probe.d.ts +42 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +63 -0
- package/dist/probe.js.map +1 -0
- package/dist/provider.d.ts +33 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +7 -0
- package/dist/provider.js.map +1 -0
- package/dist/register-service.d.ts +25 -0
- package/dist/register-service.d.ts.map +1 -0
- package/dist/register-service.js +33 -0
- package/dist/register-service.js.map +1 -0
- package/dist/sapi-tts.d.ts +29 -0
- package/dist/sapi-tts.d.ts.map +1 -0
- package/dist/sapi-tts.js +131 -0
- package/dist/sapi-tts.js.map +1 -0
- package/dist/self-check.d.ts +32 -0
- package/dist/self-check.d.ts.map +1 -0
- package/dist/self-check.js +75 -0
- package/dist/self-check.js.map +1 -0
- package/dist/service.d.ts +119 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +404 -0
- package/dist/service.js.map +1 -0
- package/dist/voices.d.ts +21 -0
- package/dist/voices.d.ts.map +1 -0
- package/dist/voices.js +74 -0
- package/dist/voices.js.map +1 -0
- package/icon.svg +6 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +93 -3
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ErrorCode, isProtocolDrift, TtsError } from "./errors.js";
|
|
2
|
+
/** Defaults shared by the config schema and `resolveConfig`. */
|
|
3
|
+
export const DEFAULT_ENGINE_HEALTH = Object.freeze({
|
|
4
|
+
enabled: true,
|
|
5
|
+
failureThreshold: 3,
|
|
6
|
+
driftCooldownMs: 300_000,
|
|
7
|
+
transientCooldownMs: 60_000,
|
|
8
|
+
maxCooldownMs: 600_000,
|
|
9
|
+
});
|
|
10
|
+
/** Engines that participate in health tracking, in chain order. */
|
|
11
|
+
const TRACKED_ENGINES = ['edge-tts', 'sapi', 'speechSynthesis'];
|
|
12
|
+
/** Failure codes that say "the engine failed", as opposed to "the caller erred". */
|
|
13
|
+
const COUNTED_FAILURES = new Set([
|
|
14
|
+
ErrorCode.EdgeTtsFailed,
|
|
15
|
+
ErrorCode.SynthesisTimeout,
|
|
16
|
+
ErrorCode.EmptyAudio,
|
|
17
|
+
ErrorCode.SpawnFailed,
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Whether a failure should move the circuit. Validation, config, cache and
|
|
21
|
+
* abort errors describe the request, not the engine; `SPAWN_EPERM` and a
|
|
22
|
+
* missing binary are environment/install problems that a circuit cannot fix.
|
|
23
|
+
*/
|
|
24
|
+
export function countsAsEngineFailure(error) {
|
|
25
|
+
if (error instanceof TtsError)
|
|
26
|
+
return COUNTED_FAILURES.has(error.code);
|
|
27
|
+
// An engine that threw something unclassified still failed to produce audio.
|
|
28
|
+
return error instanceof Error;
|
|
29
|
+
}
|
|
30
|
+
function freshRecord(transientCooldownMs) {
|
|
31
|
+
return {
|
|
32
|
+
state: 'healthy',
|
|
33
|
+
consecutiveFailures: 0,
|
|
34
|
+
cooldownMs: transientCooldownMs,
|
|
35
|
+
probePending: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One mutable registry per tool instance. `now` is injectable so tests can
|
|
40
|
+
* advance the cooldown without sleeping.
|
|
41
|
+
*/
|
|
42
|
+
export class EngineHealthRegistry {
|
|
43
|
+
#config;
|
|
44
|
+
#now;
|
|
45
|
+
#engines = new Map();
|
|
46
|
+
constructor(config = { ...DEFAULT_ENGINE_HEALTH }, now = () => new Date()) {
|
|
47
|
+
this.#config = config;
|
|
48
|
+
this.#now = now;
|
|
49
|
+
}
|
|
50
|
+
/** Current state without side effects; unknown engines are `healthy`. */
|
|
51
|
+
stateOf(engine) {
|
|
52
|
+
return this.#record(engine).state;
|
|
53
|
+
}
|
|
54
|
+
/** Full record, exposed for diagnostics and unit tests. */
|
|
55
|
+
healthOf(engine) {
|
|
56
|
+
return this.#record(engine);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Whether `engine` must be skipped for this attempt. When an open circuit's
|
|
60
|
+
* cooldown has elapsed the call is let through and marked as a probe.
|
|
61
|
+
*/
|
|
62
|
+
shouldSkip(engine) {
|
|
63
|
+
if (!this.#config.enabled)
|
|
64
|
+
return false;
|
|
65
|
+
const record = this.#record(engine);
|
|
66
|
+
if (record.state !== 'open')
|
|
67
|
+
return false;
|
|
68
|
+
const openedAt = record.openedAt ?? 0;
|
|
69
|
+
if (this.#nowMs() < openedAt + record.cooldownMs)
|
|
70
|
+
return true;
|
|
71
|
+
record.probePending = true;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Plan a whole chain at once. If every engine is open the last one is forced
|
|
76
|
+
* through: a fully open chain must still attempt something rather than fail
|
|
77
|
+
* silently with an empty result.
|
|
78
|
+
*/
|
|
79
|
+
plan(chain) {
|
|
80
|
+
const observedAt = this.#nowMs();
|
|
81
|
+
const skip = new Set();
|
|
82
|
+
if (!this.#config.enabled)
|
|
83
|
+
return { skip, observedAt };
|
|
84
|
+
const unique = [...new Set(chain)];
|
|
85
|
+
for (const engine of unique) {
|
|
86
|
+
if (this.shouldSkip(engine))
|
|
87
|
+
skip.add(engine);
|
|
88
|
+
}
|
|
89
|
+
if (unique.length > 0 && skip.size === unique.length) {
|
|
90
|
+
const last = unique[unique.length - 1];
|
|
91
|
+
if (last !== undefined)
|
|
92
|
+
skip.delete(last);
|
|
93
|
+
}
|
|
94
|
+
return { skip, observedAt };
|
|
95
|
+
}
|
|
96
|
+
/** A successful attempt closes the circuit and clears the failure streak. */
|
|
97
|
+
recordSuccess(engine) {
|
|
98
|
+
const record = this.#record(engine);
|
|
99
|
+
record.state = 'healthy';
|
|
100
|
+
record.consecutiveFailures = 0;
|
|
101
|
+
record.probePending = false;
|
|
102
|
+
delete record.openedAt;
|
|
103
|
+
record.cooldownMs = this.#config.transientCooldownMs;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Record a failed attempt. Drift opens immediately with the long cooldown;
|
|
107
|
+
* transient failures accumulate until `failureThreshold`. A failed half-open
|
|
108
|
+
* probe re-opens with a doubled cooldown, capped at `maxCooldownMs`.
|
|
109
|
+
*/
|
|
110
|
+
recordFailure(engine, error) {
|
|
111
|
+
if (!this.#config.enabled || !countsAsEngineFailure(error))
|
|
112
|
+
return;
|
|
113
|
+
const record = this.#record(engine);
|
|
114
|
+
const now = this.#nowMs();
|
|
115
|
+
record.consecutiveFailures += 1;
|
|
116
|
+
if (error instanceof TtsError)
|
|
117
|
+
record.lastErrorCode = error.code;
|
|
118
|
+
if (record.probePending) {
|
|
119
|
+
record.probePending = false;
|
|
120
|
+
record.state = 'open';
|
|
121
|
+
record.openedAt = now;
|
|
122
|
+
record.cooldownMs = Math.min(record.cooldownMs * 2, this.#config.maxCooldownMs);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (isProtocolDrift(error)) {
|
|
126
|
+
record.state = 'open';
|
|
127
|
+
record.openedAt = now;
|
|
128
|
+
record.cooldownMs = this.#config.driftCooldownMs;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (record.consecutiveFailures >= this.#config.failureThreshold) {
|
|
132
|
+
record.state = 'open';
|
|
133
|
+
record.openedAt = now;
|
|
134
|
+
record.cooldownMs = this.#config.transientCooldownMs;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
record.state = 'degraded';
|
|
138
|
+
}
|
|
139
|
+
/** Snapshot for `tts_self_check`; never performs I/O. */
|
|
140
|
+
snapshot() {
|
|
141
|
+
const out = {};
|
|
142
|
+
for (const engine of TRACKED_ENGINES) {
|
|
143
|
+
const record = this.#record(engine);
|
|
144
|
+
out[engine] =
|
|
145
|
+
record.lastErrorCode === undefined
|
|
146
|
+
? {
|
|
147
|
+
state: record.state,
|
|
148
|
+
consecutiveFailures: record.consecutiveFailures,
|
|
149
|
+
}
|
|
150
|
+
: {
|
|
151
|
+
state: record.state,
|
|
152
|
+
consecutiveFailures: record.consecutiveFailures,
|
|
153
|
+
lastErrorCode: record.lastErrorCode,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
#nowMs() {
|
|
159
|
+
return this.#now().getTime();
|
|
160
|
+
}
|
|
161
|
+
#record(engine) {
|
|
162
|
+
let record = this.#engines.get(engine);
|
|
163
|
+
if (record === undefined) {
|
|
164
|
+
record = freshRecord(this.#config.transientCooldownMs);
|
|
165
|
+
this.#engines.set(engine, record);
|
|
166
|
+
}
|
|
167
|
+
return record;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=engine-health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine-health.js","sourceRoot":"","sources":["../src/engine-health.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** One word boundary as Edge reports it. */
|
|
2
|
+
export interface EdgeJsWord {
|
|
3
|
+
text: string;
|
|
4
|
+
offsetMs: number;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
}
|
|
7
|
+
/** One synthesis request. */
|
|
8
|
+
export interface EdgeJsRequest {
|
|
9
|
+
text: string;
|
|
10
|
+
voice: string;
|
|
11
|
+
/** `+0%` style, passed through to SSML. */
|
|
12
|
+
rate?: string;
|
|
13
|
+
/** `+0Hz` style. */
|
|
14
|
+
pitch?: string;
|
|
15
|
+
/** `+0%` style. */
|
|
16
|
+
volume?: string;
|
|
17
|
+
/** Forwarded to the WSS handshake; Edge ignores HTTPS_PROXY. */
|
|
18
|
+
proxy?: string;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
/** Audio plus the timing metadata Edge reports for it. */
|
|
22
|
+
export interface EdgeJsResult {
|
|
23
|
+
audio: Buffer;
|
|
24
|
+
words: EdgeJsWord[];
|
|
25
|
+
}
|
|
26
|
+
/** The shape `node-edge-tts` exposes; kept structural so the version can drift. */
|
|
27
|
+
interface EdgeJsInstance {
|
|
28
|
+
streamAudio(text: string): AsyncGenerator<Buffer | {
|
|
29
|
+
part: string;
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
}>;
|
|
33
|
+
ttsPromise(text: string, audioPath: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
interface EdgeJsModule {
|
|
36
|
+
EdgeTTS: new (config: Record<string, unknown>) => EdgeJsInstance;
|
|
37
|
+
}
|
|
38
|
+
/** Replace the module loader. Tests inject a fake; production clears it. */
|
|
39
|
+
export declare function setEdgeJsLoader(impl: (() => Promise<unknown>) | undefined): void;
|
|
40
|
+
/** Load the optional dependency, or report that it is not installed. */
|
|
41
|
+
export declare function loadEdgeJsModule(): Promise<EdgeJsModule | undefined>;
|
|
42
|
+
/** Whether the pure-JS backend can be used in this process. */
|
|
43
|
+
export declare function isEdgeJsAvailable(): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Synthesize once and collect audio plus word boundaries. Word offsets are
|
|
46
|
+
* absolute from the start of this call's audio, which is what callers need to
|
|
47
|
+
* shift per chunk.
|
|
48
|
+
*/
|
|
49
|
+
export declare function synthesizeWithEdgeJs(request: EdgeJsRequest): Promise<EdgeJsResult>;
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=edge-js.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-js.d.ts","sourceRoot":"","sources":["../../src/engines/edge-js.ts"],"names":[],"mappings":"AAaA,4CAA4C;AAC5C,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,6BAA6B;AAC7B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mBAAmB;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,UAAU,EAAE,CAAA;CACpB;AAED,mFAAmF;AACnF,UAAU,cAAc;IACtB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3D;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,cAAc,CAAA;CACjE;AAID,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,IAAI,EAAE,CAAC,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS,GAAG,IAAI,CAEhF;AAED,wEAAwE;AACxE,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAQ1E;AAED,+DAA+D;AAC/D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AA8BD;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAuDxF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-JS synthesis backend over `node-edge-tts`.
|
|
3
|
+
*
|
|
4
|
+
* Why this exists: the CLI backend requires Python, which is the single largest
|
|
5
|
+
* install obstacle for a DSH plugin. This adapter removes it, and it also
|
|
6
|
+
* returns word boundaries, which upgrades subtitle timing from "estimated" to
|
|
7
|
+
* "exact".
|
|
8
|
+
*
|
|
9
|
+
* The module is an optional dependency: a host without it still gets the CLI
|
|
10
|
+
* path, so the import is dynamic and the loader is injectable for tests.
|
|
11
|
+
*/
|
|
12
|
+
import { ErrorCode, looksLikeThrottle, TtsError } from "../errors.js";
|
|
13
|
+
let loader;
|
|
14
|
+
/** Replace the module loader. Tests inject a fake; production clears it. */
|
|
15
|
+
export function setEdgeJsLoader(impl) {
|
|
16
|
+
loader = impl;
|
|
17
|
+
}
|
|
18
|
+
/** Load the optional dependency, or report that it is not installed. */
|
|
19
|
+
export async function loadEdgeJsModule() {
|
|
20
|
+
const load = loader ?? (() => import('node-edge-tts'));
|
|
21
|
+
try {
|
|
22
|
+
const loaded = (await load());
|
|
23
|
+
return typeof loaded.EdgeTTS === 'function' ? loaded : undefined;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Whether the pure-JS backend can be used in this process. */
|
|
30
|
+
export async function isEdgeJsAvailable() {
|
|
31
|
+
return (await loadEdgeJsModule()) !== undefined;
|
|
32
|
+
}
|
|
33
|
+
/** Edge-TTS voices are `<lang>-<REGION>-<Name>Neural`; the language is the prefix. */
|
|
34
|
+
function languageOf(voice) {
|
|
35
|
+
const parts = voice.split('-');
|
|
36
|
+
return parts.length >= 2 ? `${parts[0]}-${parts[1]}` : 'zh-CN';
|
|
37
|
+
}
|
|
38
|
+
/** Classify a backend failure as protocol drift when the handshake is rejected. */
|
|
39
|
+
function asTtsError(error) {
|
|
40
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
41
|
+
if (looksLikeThrottle(detail)) {
|
|
42
|
+
return new TtsError(ErrorCode.UpstreamThrottled, `The Edge endpoint is throttling requests: ${detail}`, 'Retry shortly. The engine backs off automatically before falling back to another voice engine.', { cause: error });
|
|
43
|
+
}
|
|
44
|
+
const drift = /403|handshake|sec-ms-gec|invalid response status/i.test(detail);
|
|
45
|
+
return new TtsError(ErrorCode.EdgeTtsFailed, `The pure-JS Edge backend failed: ${detail}`, drift
|
|
46
|
+
? 'The Edge handshake changed. Upgrade `node-edge-tts`, or pin the CLI backend with engine=edge-py.'
|
|
47
|
+
: 'Check the network and the configured proxy; run tts_probe_network to test reachability.', { cause: error, protocolDrift: drift });
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Synthesize once and collect audio plus word boundaries. Word offsets are
|
|
51
|
+
* absolute from the start of this call's audio, which is what callers need to
|
|
52
|
+
* shift per chunk.
|
|
53
|
+
*/
|
|
54
|
+
export async function synthesizeWithEdgeJs(request) {
|
|
55
|
+
const module = await loadEdgeJsModule();
|
|
56
|
+
if (module === undefined) {
|
|
57
|
+
throw new TtsError(ErrorCode.EngineUnavailable, 'The pure-JS Edge backend is not installed (node-edge-tts is missing).', 'Install it with `npm i node-edge-tts`, or set engine=edge-py to use the CLI backend.');
|
|
58
|
+
}
|
|
59
|
+
const instance = new module.EdgeTTS({
|
|
60
|
+
voice: request.voice,
|
|
61
|
+
lang: languageOf(request.voice),
|
|
62
|
+
outputFormat: 'audio-24khz-48kbitrate-mono-mp3',
|
|
63
|
+
// `saveSubtitles` gates *parsing* of the WordBoundary metadata, not file
|
|
64
|
+
// writes: `streamAudio()` yields the boundaries and never touches disk.
|
|
65
|
+
// Without it the generator yields audio only and timing stays estimated.
|
|
66
|
+
saveSubtitles: true,
|
|
67
|
+
...(request.proxy === undefined ? {} : { proxy: request.proxy }),
|
|
68
|
+
...(request.rate === undefined ? {} : { rate: request.rate }),
|
|
69
|
+
...(request.pitch === undefined ? {} : { pitch: request.pitch }),
|
|
70
|
+
...(request.volume === undefined ? {} : { volume: request.volume }),
|
|
71
|
+
...(request.timeoutMs === undefined ? {} : { timeout: request.timeoutMs }),
|
|
72
|
+
});
|
|
73
|
+
const chunks = [];
|
|
74
|
+
const words = [];
|
|
75
|
+
try {
|
|
76
|
+
for await (const item of instance.streamAudio(request.text)) {
|
|
77
|
+
if (Buffer.isBuffer(item)) {
|
|
78
|
+
chunks.push(item);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (typeof item === 'object' && item !== null && 'part' in item) {
|
|
82
|
+
const boundary = item;
|
|
83
|
+
const durationMs = Math.max(0, boundary.end - boundary.start);
|
|
84
|
+
if (boundary.part.length > 0) {
|
|
85
|
+
words.push({ text: boundary.part, offsetMs: boundary.start, durationMs });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
throw asTtsError(error);
|
|
92
|
+
}
|
|
93
|
+
if (chunks.length === 0) {
|
|
94
|
+
throw new TtsError(ErrorCode.EmptyAudio, 'The pure-JS Edge backend returned no audio bytes.', 'Retry; if it persists, run tts_self_check and tts_probe_network.');
|
|
95
|
+
}
|
|
96
|
+
return { audio: Buffer.concat(chunks), words };
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=edge-js.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edge-js.js","sourceRoot":"","sources":["../../src/engines/edge-js.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
/**
|
|
3
|
+
* Stable, machine-readable failure codes. Callers route on these codes instead
|
|
4
|
+
* of parsing human-facing messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ErrorCode: {
|
|
7
|
+
readonly EmptyText: "EMPTY_TEXT";
|
|
8
|
+
readonly InvalidVoice: "INVALID_VOICE";
|
|
9
|
+
readonly InvalidRate: "INVALID_RATE";
|
|
10
|
+
readonly InvalidPitch: "INVALID_PITCH";
|
|
11
|
+
readonly InvalidVolume: "INVALID_VOLUME";
|
|
12
|
+
readonly InvalidConfig: "INVALID_CONFIG";
|
|
13
|
+
readonly DshHomeNotSet: "DSH_HOME_NOT_SET";
|
|
14
|
+
readonly OutputDirNotWritable: "OUTPUT_DIR_NOT_WRITABLE";
|
|
15
|
+
readonly EdgeTtsNotFound: "EDGE_TTS_NOT_FOUND";
|
|
16
|
+
readonly EngineUnavailable: "ENGINE_UNAVAILABLE";
|
|
17
|
+
readonly SpawnEperm: "SPAWN_EPERM";
|
|
18
|
+
readonly SpawnFailed: "SPAWN_FAILED";
|
|
19
|
+
readonly EdgeTtsFailed: "EDGE_TTS_FAILED";
|
|
20
|
+
readonly UpstreamThrottled: "UPSTREAM_THROTTLED";
|
|
21
|
+
readonly SynthesisTimeout: "SYNTHESIS_TIMEOUT";
|
|
22
|
+
readonly EmptyAudio: "EMPTY_AUDIO";
|
|
23
|
+
readonly Aborted: "ABORTED";
|
|
24
|
+
readonly TtsBusy: "TTS_BUSY";
|
|
25
|
+
readonly AllFallbacksFailed: "ALL_FALLBACKS_FAILED";
|
|
26
|
+
readonly SpeechSynthesisRequired: "SPEECH_SYNTHESIS_REQUIRED";
|
|
27
|
+
};
|
|
28
|
+
export type TtsErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
29
|
+
/** Whether captured subprocess output looks like protocol drift, not jitter. */
|
|
30
|
+
export declare function looksLikeProtocolDrift(text: string): boolean;
|
|
31
|
+
/** Whether captured output looks like upstream throttling. */
|
|
32
|
+
export declare function looksLikeThrottle(text: string): boolean;
|
|
33
|
+
/** Options accepted by {@link TtsError}. */
|
|
34
|
+
export interface TtsErrorOptions extends ErrorOptions {
|
|
35
|
+
/** Marks a failure caused by upstream protocol drift rather than jitter. */
|
|
36
|
+
protocolDrift?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Structured plugin error: a routable `code`, a human-readable message, and an
|
|
40
|
+
* actionable suggestion. Extends the harness error base so the tool registry
|
|
41
|
+
* can retain the code in its failure metadata.
|
|
42
|
+
*/
|
|
43
|
+
export declare class TtsError extends HarnessError {
|
|
44
|
+
/** Pre-suggestion message, kept verbatim for structured consumers. */
|
|
45
|
+
readonly detail: string;
|
|
46
|
+
/** Action the caller (or the user) can take to resolve the failure. */
|
|
47
|
+
readonly suggestion?: string;
|
|
48
|
+
/** Set when the failure is a handshake/protocol rejection, not a timeout. */
|
|
49
|
+
readonly protocolDrift: boolean;
|
|
50
|
+
constructor(code: TtsErrorCode, message: string, suggestion?: string, options?: TtsErrorOptions);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Classify a failure as protocol drift. The explicit flag wins; the text
|
|
54
|
+
* fallback keeps errors re-wrapped by callers (for example per-chunk failures)
|
|
55
|
+
* classifiable without extra plumbing.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isProtocolDrift(error: unknown): boolean;
|
|
58
|
+
/** Read a Node `SystemError` code without assuming the thrown value's shape. */
|
|
59
|
+
export declare function errnoCode(error: unknown): string | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Translate a failed `spawn()` attempt into a structured error. `EPERM` is
|
|
62
|
+
* deliberately distinct: it means the sandbox refused process creation, not
|
|
63
|
+
* that the output directory is unwritable.
|
|
64
|
+
*/
|
|
65
|
+
export declare function spawnFailure(error: unknown): TtsError;
|
|
66
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;CAqBZ,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAA;AAgBrE,gFAAgF;AAChF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D;AASD,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,4CAA4C;AAC5C,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,4EAA4E;IAC5E,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;GAIG;AACH,qBAAa,QAAS,SAAQ,YAAY;IACxC,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,uEAAuE;IACvE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;gBAG7B,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EACf,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,eAAe;CAQ5B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGvD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAM5D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CA4BrD"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
/**
|
|
3
|
+
* Stable, machine-readable failure codes. Callers route on these codes instead
|
|
4
|
+
* of parsing human-facing messages.
|
|
5
|
+
*/
|
|
6
|
+
export const ErrorCode = {
|
|
7
|
+
EmptyText: 'EMPTY_TEXT',
|
|
8
|
+
InvalidVoice: 'INVALID_VOICE',
|
|
9
|
+
InvalidRate: 'INVALID_RATE',
|
|
10
|
+
InvalidPitch: 'INVALID_PITCH',
|
|
11
|
+
InvalidVolume: 'INVALID_VOLUME',
|
|
12
|
+
InvalidConfig: 'INVALID_CONFIG',
|
|
13
|
+
DshHomeNotSet: 'DSH_HOME_NOT_SET',
|
|
14
|
+
OutputDirNotWritable: 'OUTPUT_DIR_NOT_WRITABLE',
|
|
15
|
+
EdgeTtsNotFound: 'EDGE_TTS_NOT_FOUND',
|
|
16
|
+
EngineUnavailable: 'ENGINE_UNAVAILABLE',
|
|
17
|
+
SpawnEperm: 'SPAWN_EPERM',
|
|
18
|
+
SpawnFailed: 'SPAWN_FAILED',
|
|
19
|
+
EdgeTtsFailed: 'EDGE_TTS_FAILED',
|
|
20
|
+
UpstreamThrottled: 'UPSTREAM_THROTTLED',
|
|
21
|
+
SynthesisTimeout: 'SYNTHESIS_TIMEOUT',
|
|
22
|
+
EmptyAudio: 'EMPTY_AUDIO',
|
|
23
|
+
Aborted: 'ABORTED',
|
|
24
|
+
TtsBusy: 'TTS_BUSY',
|
|
25
|
+
AllFallbacksFailed: 'ALL_FALLBACKS_FAILED',
|
|
26
|
+
SpeechSynthesisRequired: 'SPEECH_SYNTHESIS_REQUIRED',
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Substrings that mean the upstream Edge-TTS protocol changed under us: a
|
|
30
|
+
* rejected WebSocket handshake, a rotated `Sec-MS-GEC` token generation, or a
|
|
31
|
+
* generic HTTP 403. These are not retryable jitter — the installed edge-tts
|
|
32
|
+
* version and the service disagree about the handshake.
|
|
33
|
+
*/
|
|
34
|
+
const PROTOCOL_DRIFT_PATTERNS = [
|
|
35
|
+
/403/,
|
|
36
|
+
/invalid response status/i,
|
|
37
|
+
/wsserverhandshakeerror/i,
|
|
38
|
+
/sec-ms-gec/i,
|
|
39
|
+
/handshake/i,
|
|
40
|
+
];
|
|
41
|
+
/** Whether captured subprocess output looks like protocol drift, not jitter. */
|
|
42
|
+
export function looksLikeProtocolDrift(text) {
|
|
43
|
+
return PROTOCOL_DRIFT_PATTERNS.some((pattern) => pattern.test(text));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Substrings that mean the endpoint is rate limiting us rather than failing.
|
|
47
|
+
* Throttling is transient and worth a backoff; falling back to another engine
|
|
48
|
+
* would only hide the load problem and produce worse audio.
|
|
49
|
+
*/
|
|
50
|
+
const THROTTLE_PATTERNS = [/\b429\b/, /too many requests/i, /rate limit/i];
|
|
51
|
+
/** Whether captured output looks like upstream throttling. */
|
|
52
|
+
export function looksLikeThrottle(text) {
|
|
53
|
+
return THROTTLE_PATTERNS.some((pattern) => pattern.test(text));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Structured plugin error: a routable `code`, a human-readable message, and an
|
|
57
|
+
* actionable suggestion. Extends the harness error base so the tool registry
|
|
58
|
+
* can retain the code in its failure metadata.
|
|
59
|
+
*/
|
|
60
|
+
export class TtsError extends HarnessError {
|
|
61
|
+
/** Pre-suggestion message, kept verbatim for structured consumers. */
|
|
62
|
+
detail;
|
|
63
|
+
/** Action the caller (or the user) can take to resolve the failure. */
|
|
64
|
+
suggestion;
|
|
65
|
+
/** Set when the failure is a handshake/protocol rejection, not a timeout. */
|
|
66
|
+
protocolDrift;
|
|
67
|
+
constructor(code, message, suggestion, options) {
|
|
68
|
+
super(suggestion ? `${message}\nSuggestion: ${suggestion}` : message, code, options);
|
|
69
|
+
this.name = 'TtsError';
|
|
70
|
+
this.detail = message;
|
|
71
|
+
this.suggestion = suggestion;
|
|
72
|
+
this.protocolDrift = options?.protocolDrift === true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Classify a failure as protocol drift. The explicit flag wins; the text
|
|
77
|
+
* fallback keeps errors re-wrapped by callers (for example per-chunk failures)
|
|
78
|
+
* classifiable without extra plumbing.
|
|
79
|
+
*/
|
|
80
|
+
export function isProtocolDrift(error) {
|
|
81
|
+
if (!(error instanceof TtsError))
|
|
82
|
+
return false;
|
|
83
|
+
return error.protocolDrift || looksLikeProtocolDrift(error.detail);
|
|
84
|
+
}
|
|
85
|
+
/** Read a Node `SystemError` code without assuming the thrown value's shape. */
|
|
86
|
+
export function errnoCode(error) {
|
|
87
|
+
if (typeof error === 'object' && error !== null && 'code' in error) {
|
|
88
|
+
const code = error.code;
|
|
89
|
+
if (typeof code === 'string')
|
|
90
|
+
return code;
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Translate a failed `spawn()` attempt into a structured error. `EPERM` is
|
|
96
|
+
* deliberately distinct: it means the sandbox refused process creation, not
|
|
97
|
+
* that the output directory is unwritable.
|
|
98
|
+
*/
|
|
99
|
+
export function spawnFailure(error) {
|
|
100
|
+
const code = errnoCode(error);
|
|
101
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
102
|
+
if (code === 'ENOENT') {
|
|
103
|
+
return new TtsError(ErrorCode.EdgeTtsNotFound, `Cannot start Edge-TTS: ${detail}`, 'Install it with `pip install edge-tts`, or point `edgeTtsBinary` at the executable.', { cause: error });
|
|
104
|
+
}
|
|
105
|
+
if (code === 'EPERM') {
|
|
106
|
+
return new TtsError(ErrorCode.SpawnEperm, `The DSH sandbox denied process creation for Edge-TTS (spawn EPERM): ${detail}`, 'Raise the DSH sandbox process/file policy and retry. This is a sandbox policy denial, not a missing or unwritable output directory.', { cause: error });
|
|
107
|
+
}
|
|
108
|
+
return new TtsError(ErrorCode.SpawnFailed, `Failed to spawn Edge-TTS (${code ?? 'unknown error'}): ${detail}`, 'Check that `edgeTtsBinary` names an existing, executable Edge-TTS CLI.', { cause: error });
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fallback chain orchestration: Edge-TTS is the primary engine, and on an
|
|
3
|
+
* eligible failure the configured chain is tried in order. SAPI is Windows-only;
|
|
4
|
+
* the browser `speechSynthesis` entry produces a marker instead of a file (the
|
|
5
|
+
* host cannot produce audio without an engine). A chain with no reachable
|
|
6
|
+
* engine ends as `ALL_FALLBACKS_FAILED` listing every engine and reason.
|
|
7
|
+
*/
|
|
8
|
+
import type { TtsEngine } from './config.ts';
|
|
9
|
+
import type { SpawnFunction, SynthesizedAudio } from './edge-tts.ts';
|
|
10
|
+
import type { EngineHealthHooks, EngineHealthState } from './engine-health.ts';
|
|
11
|
+
import { TtsError } from './errors.ts';
|
|
12
|
+
import { type SapiOptions } from './sapi-tts.ts';
|
|
13
|
+
/** Whether a failed Edge-TTS attempt is worth retrying with another engine. */
|
|
14
|
+
export declare function isFallbackEligible(error: unknown): error is TtsError;
|
|
15
|
+
/** One engine that was skipped or failed, with a human-readable reason. */
|
|
16
|
+
export interface FallbackAttempt {
|
|
17
|
+
engine: TtsEngine;
|
|
18
|
+
reason: string;
|
|
19
|
+
}
|
|
20
|
+
/** Reason recorded when the health circuit skips an engine. */
|
|
21
|
+
export declare const SKIPPED_BY_HEALTH = "skipped: circuit open (cooldown)";
|
|
22
|
+
/** Either a produced file or a request for the client to speak the text. */
|
|
23
|
+
export type FallbackOutcome = {
|
|
24
|
+
kind: 'audio';
|
|
25
|
+
audio: SynthesizedAudio;
|
|
26
|
+
engine: TtsEngine;
|
|
27
|
+
/** Health of `engine` just before this attempt succeeded. */
|
|
28
|
+
engineState: EngineHealthState;
|
|
29
|
+
attempts: FallbackAttempt[];
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'speechSynthesis';
|
|
32
|
+
engine: 'speechSynthesis';
|
|
33
|
+
attempts: FallbackAttempt[];
|
|
34
|
+
};
|
|
35
|
+
export interface FallbackOptions {
|
|
36
|
+
text: string;
|
|
37
|
+
rate: string;
|
|
38
|
+
outputDir: string;
|
|
39
|
+
hash: string;
|
|
40
|
+
timeoutMs: number;
|
|
41
|
+
fallbackOrder: readonly TtsEngine[];
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
now?: Date;
|
|
44
|
+
/** Error thrown by the primary Edge-TTS attempt, kept for the final message. */
|
|
45
|
+
primaryError?: unknown;
|
|
46
|
+
/** Why the primary attempt was not made, when its circuit was open. */
|
|
47
|
+
primaryReason?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Engines whose circuit is open. Skipping never reorders the chain; it only
|
|
50
|
+
* removes an engine from this attempt.
|
|
51
|
+
*/
|
|
52
|
+
skip?: ReadonlySet<TtsEngine>;
|
|
53
|
+
}
|
|
54
|
+
/** Injection points; production uses the real Windows/ffmpeg binaries. */
|
|
55
|
+
export interface FallbackDependencies {
|
|
56
|
+
platform?: NodeJS.Platform;
|
|
57
|
+
spawnImpl?: SpawnFunction;
|
|
58
|
+
powershellBinary?: string;
|
|
59
|
+
ffmpegBinary?: string;
|
|
60
|
+
synthesizeSapiImpl?: (text: string, options: SapiOptions) => Promise<SynthesizedAudio>;
|
|
61
|
+
/** Health hooks; omitted by direct callers that do not track engines. */
|
|
62
|
+
health?: EngineHealthHooks;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Run the configured engines after Edge-TTS already failed. `edge-tts` in
|
|
66
|
+
* `fallbackOrder` marks the primary attempt rather than a retry.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runFallback(options: FallbackOptions, deps?: FallbackDependencies): Promise<FallbackOutcome>;
|
|
69
|
+
//# sourceMappingURL=fallback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../src/fallback.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC9E,OAAO,EAAa,QAAQ,EAAqB,MAAM,aAAa,CAAA;AACpE,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,eAAe,CAAA;AAYtB,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAEpE;AAED,2EAA2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,qCAAqC,CAAA;AAEnE,4EAA4E;AAC5E,MAAM,MAAM,eAAe,GACvB;IACE,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,gBAAgB,CAAA;IACvB,MAAM,EAAE,SAAS,CAAA;IACjB,6DAA6D;IAC7D,WAAW,EAAE,iBAAiB,CAAA;IAC9B,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B,GACD;IACE,IAAI,EAAE,iBAAiB,CAAA;IACvB,MAAM,EAAE,iBAAiB,CAAA;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAA;CAC5B,CAAA;AAEL,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,SAAS,SAAS,EAAE,CAAA;IACnC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,GAAG,CAAC,EAAE,IAAI,CAAA;IACV,gFAAgF;IAChF,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;CAC9B;AAED,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,CACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC9B,yEAAyE;IACzE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B;AAuBD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,eAAe,EACxB,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,eAAe,CAAC,CA2D1B"}
|