@framers/agentos 0.1.226 → 0.1.228

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 (79) hide show
  1. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
  2. package/dist/core/llm/providers/implementations/AnthropicProvider.js +71 -12
  3. package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
  4. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
  5. package/dist/core/llm/providers/implementations/OpenAIProvider.js +38 -11
  6. package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
  7. package/dist/media/images/face/ReplicateFaceEmbeddingService.d.ts +7 -0
  8. package/dist/media/images/face/ReplicateFaceEmbeddingService.d.ts.map +1 -1
  9. package/dist/media/images/face/ReplicateFaceEmbeddingService.js +32 -5
  10. package/dist/media/images/face/ReplicateFaceEmbeddingService.js.map +1 -1
  11. package/dist/voice-pipeline/AudioRingBuffer.d.ts +28 -0
  12. package/dist/voice-pipeline/AudioRingBuffer.d.ts.map +1 -0
  13. package/dist/voice-pipeline/AudioRingBuffer.js +43 -0
  14. package/dist/voice-pipeline/AudioRingBuffer.js.map +1 -0
  15. package/dist/voice-pipeline/CircuitBreaker.d.ts +42 -0
  16. package/dist/voice-pipeline/CircuitBreaker.d.ts.map +1 -0
  17. package/dist/voice-pipeline/CircuitBreaker.js +96 -0
  18. package/dist/voice-pipeline/CircuitBreaker.js.map +1 -0
  19. package/dist/voice-pipeline/HealthyProvider.d.ts +44 -0
  20. package/dist/voice-pipeline/HealthyProvider.d.ts.map +1 -0
  21. package/dist/voice-pipeline/HealthyProvider.js +29 -0
  22. package/dist/voice-pipeline/HealthyProvider.js.map +1 -0
  23. package/dist/voice-pipeline/TranscriptDedupe.d.ts +43 -0
  24. package/dist/voice-pipeline/TranscriptDedupe.d.ts.map +1 -0
  25. package/dist/voice-pipeline/TranscriptDedupe.js +96 -0
  26. package/dist/voice-pipeline/TranscriptDedupe.js.map +1 -0
  27. package/dist/voice-pipeline/VoiceMetricsReporter.d.ts +48 -0
  28. package/dist/voice-pipeline/VoiceMetricsReporter.d.ts.map +1 -0
  29. package/dist/voice-pipeline/VoiceMetricsReporter.js +33 -0
  30. package/dist/voice-pipeline/VoiceMetricsReporter.js.map +1 -0
  31. package/dist/voice-pipeline/VoicePipelineError.d.ts +44 -0
  32. package/dist/voice-pipeline/VoicePipelineError.d.ts.map +1 -0
  33. package/dist/voice-pipeline/VoicePipelineError.js +74 -0
  34. package/dist/voice-pipeline/VoicePipelineError.js.map +1 -0
  35. package/dist/voice-pipeline/env-constructor.d.ts +48 -0
  36. package/dist/voice-pipeline/env-constructor.d.ts.map +1 -0
  37. package/dist/voice-pipeline/env-constructor.js +85 -0
  38. package/dist/voice-pipeline/env-constructor.js.map +1 -0
  39. package/dist/voice-pipeline/index.d.ts +8 -1
  40. package/dist/voice-pipeline/index.d.ts.map +1 -1
  41. package/dist/voice-pipeline/index.js +9 -1
  42. package/dist/voice-pipeline/index.js.map +1 -1
  43. package/dist/voice-pipeline/providers/DeepgramStreamingSTT.d.ts +24 -1
  44. package/dist/voice-pipeline/providers/DeepgramStreamingSTT.d.ts.map +1 -1
  45. package/dist/voice-pipeline/providers/DeepgramStreamingSTT.js +51 -0
  46. package/dist/voice-pipeline/providers/DeepgramStreamingSTT.js.map +1 -1
  47. package/dist/voice-pipeline/providers/ElevenLabsBatchTTS.d.ts +17 -1
  48. package/dist/voice-pipeline/providers/ElevenLabsBatchTTS.d.ts.map +1 -1
  49. package/dist/voice-pipeline/providers/ElevenLabsBatchTTS.js +46 -0
  50. package/dist/voice-pipeline/providers/ElevenLabsBatchTTS.js.map +1 -1
  51. package/dist/voice-pipeline/providers/ElevenLabsStreamingSTT.d.ts +16 -1
  52. package/dist/voice-pipeline/providers/ElevenLabsStreamingSTT.d.ts.map +1 -1
  53. package/dist/voice-pipeline/providers/ElevenLabsStreamingSTT.js +46 -0
  54. package/dist/voice-pipeline/providers/ElevenLabsStreamingSTT.js.map +1 -1
  55. package/dist/voice-pipeline/providers/ElevenLabsStreamingTTS.d.ts +16 -1
  56. package/dist/voice-pipeline/providers/ElevenLabsStreamingTTS.d.ts.map +1 -1
  57. package/dist/voice-pipeline/providers/ElevenLabsStreamingTTS.js +46 -0
  58. package/dist/voice-pipeline/providers/ElevenLabsStreamingTTS.js.map +1 -1
  59. package/dist/voice-pipeline/providers/OpenAIBatchTTS.d.ts +16 -1
  60. package/dist/voice-pipeline/providers/OpenAIBatchTTS.d.ts.map +1 -1
  61. package/dist/voice-pipeline/providers/OpenAIBatchTTS.js +46 -0
  62. package/dist/voice-pipeline/providers/OpenAIBatchTTS.js.map +1 -1
  63. package/dist/voice-pipeline/providers/OpenAIRealtimeTTS.d.ts +16 -1
  64. package/dist/voice-pipeline/providers/OpenAIRealtimeTTS.d.ts.map +1 -1
  65. package/dist/voice-pipeline/providers/OpenAIRealtimeTTS.js +46 -0
  66. package/dist/voice-pipeline/providers/OpenAIRealtimeTTS.js.map +1 -1
  67. package/dist/voice-pipeline/providers/StreamingSTTChain.d.ts +78 -0
  68. package/dist/voice-pipeline/providers/StreamingSTTChain.d.ts.map +1 -0
  69. package/dist/voice-pipeline/providers/StreamingSTTChain.js +225 -0
  70. package/dist/voice-pipeline/providers/StreamingSTTChain.js.map +1 -0
  71. package/dist/voice-pipeline/providers/StreamingTTSChain.d.ts +67 -0
  72. package/dist/voice-pipeline/providers/StreamingTTSChain.d.ts.map +1 -0
  73. package/dist/voice-pipeline/providers/StreamingTTSChain.js +212 -0
  74. package/dist/voice-pipeline/providers/StreamingTTSChain.js.map +1 -0
  75. package/dist/voice-pipeline/providers/index.d.ts +2 -0
  76. package/dist/voice-pipeline/providers/index.d.ts.map +1 -1
  77. package/dist/voice-pipeline/providers/index.js +2 -0
  78. package/dist/voice-pipeline/providers/index.js.map +1 -1
  79. package/package.json +1 -1
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module voice-pipeline/AudioRingBuffer
3
+ *
4
+ * Fixed-capacity PCM ring buffer keyed by wall-clock timestamp. Used by
5
+ * StreamingSTTChain to replay the last ~capacityMs of audio into a backup
6
+ * provider when the primary fails mid-utterance.
7
+ *
8
+ * The buffer treats each AudioFrame as atomic — it never splits frames —
9
+ * so durationMs() may exceed capacityMs by up to one frame's worth of
10
+ * audio until the next push. For a 20 ms frame size and a 3000 ms
11
+ * capacity this overshoot is negligible.
12
+ */
13
+ export class AudioRingBuffer {
14
+ constructor(opts) {
15
+ this.frames = [];
16
+ this.capacityMs = opts.capacityMs;
17
+ }
18
+ push(frame) {
19
+ this.frames.push(frame);
20
+ // Always keep at least one frame. Without this guard a pathologically
21
+ // small capacity (e.g. 1 ms) would evict every push immediately.
22
+ while (this.frames.length > 1 && this.durationMs() > this.capacityMs) {
23
+ this.frames.shift();
24
+ }
25
+ }
26
+ snapshot() {
27
+ return [...this.frames];
28
+ }
29
+ durationMs() {
30
+ if (this.frames.length === 0)
31
+ return 0;
32
+ const first = this.frames[0];
33
+ const last = this.frames[this.frames.length - 1];
34
+ return last.timestamp - first.timestamp + this.frameDurationMs(last);
35
+ }
36
+ clear() {
37
+ this.frames = [];
38
+ }
39
+ frameDurationMs(frame) {
40
+ return (frame.samples.length / frame.sampleRate) * 1000;
41
+ }
42
+ }
43
+ //# sourceMappingURL=AudioRingBuffer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AudioRingBuffer.js","sourceRoot":"","sources":["../../src/voice-pipeline/AudioRingBuffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,OAAO,eAAe;IAI1B,YAAY,IAA4B;QAFhC,WAAM,GAAiB,EAAE,CAAC;QAGhC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,KAAiB;QACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,sEAAsE;QACtE,iEAAiE;QACjE,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACrE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;IAED,UAAU;QACR,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;IAEO,eAAe,CAAC,KAAiB;QACvC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;IAC1D,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @module voice-pipeline/CircuitBreaker
3
+ *
4
+ * Per-provider state machine: tracks failures within a sliding window, trips
5
+ * when the failure count crosses a threshold, and auto-recovers after a
6
+ * cooldown. Auth failures trip permanently (cooldown = Infinity) because a
7
+ * bad API key won't fix itself without operator intervention.
8
+ */
9
+ import type { HealthErrorClass } from './VoicePipelineError.js';
10
+ export type BreakerState = 'healthy' | 'tripped';
11
+ export interface CircuitBreakerOptions {
12
+ failureThreshold: number;
13
+ windowMs: number;
14
+ cooldownMs: number;
15
+ now?: () => number;
16
+ }
17
+ export interface StateChangeEvent {
18
+ providerId: string;
19
+ from: BreakerState;
20
+ to: BreakerState;
21
+ reason?: HealthErrorClass | 'recover';
22
+ }
23
+ export declare class CircuitBreaker {
24
+ private readonly failureThreshold;
25
+ private readonly windowMs;
26
+ private readonly cooldownMs;
27
+ private readonly nowFn;
28
+ private readonly records;
29
+ private readonly listeners;
30
+ constructor(opts: CircuitBreakerOptions);
31
+ state(providerId: string): BreakerState;
32
+ isAvailable(providerId: string): boolean;
33
+ recordFailure(providerId: string, reason: HealthErrorClass): void;
34
+ recordSuccess(providerId: string): void;
35
+ /** Force a state-transition pass for all tracked providers. Useful when
36
+ * the caller wants to drive recoveries on a timer. */
37
+ tick(_nowHint?: number): void;
38
+ onStateChange(fn: (event: StateChangeEvent) => void): () => void;
39
+ private getOrCreate;
40
+ private transition;
41
+ }
42
+ //# sourceMappingURL=CircuitBreaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CircuitBreaker.d.ts","sourceRoot":"","sources":["../../src/voice-pipeline/CircuitBreaker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,YAAY,CAAC;IACjB,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CACvC;AASD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqC;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgD;gBAE9D,IAAI,EAAE,qBAAqB;IAOvC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY;IAQvC,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAIxC,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAuBjE,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAUvC;2DACuD;IACvD,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAM7B,aAAa,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAKhE,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,UAAU;CAiBnB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @module voice-pipeline/CircuitBreaker
3
+ *
4
+ * Per-provider state machine: tracks failures within a sliding window, trips
5
+ * when the failure count crosses a threshold, and auto-recovers after a
6
+ * cooldown. Auth failures trip permanently (cooldown = Infinity) because a
7
+ * bad API key won't fix itself without operator intervention.
8
+ */
9
+ export class CircuitBreaker {
10
+ constructor(opts) {
11
+ this.records = new Map();
12
+ this.listeners = new Set();
13
+ this.failureThreshold = opts.failureThreshold;
14
+ this.windowMs = opts.windowMs;
15
+ this.cooldownMs = opts.cooldownMs;
16
+ this.nowFn = opts.now ?? (() => Date.now());
17
+ }
18
+ state(providerId) {
19
+ const rec = this.getOrCreate(providerId);
20
+ if (rec.currentState === 'tripped' && this.nowFn() >= rec.trippedUntil) {
21
+ this.transition(providerId, rec, 'healthy', 'recover');
22
+ }
23
+ return rec.currentState;
24
+ }
25
+ isAvailable(providerId) {
26
+ return this.state(providerId) === 'healthy';
27
+ }
28
+ recordFailure(providerId, reason) {
29
+ const rec = this.getOrCreate(providerId);
30
+ const now = this.nowFn();
31
+ // Auth failures are terminal — a bad key won't recover on its own.
32
+ if (reason === 'auth') {
33
+ rec.trippedAt = now;
34
+ rec.trippedUntil = Number.POSITIVE_INFINITY;
35
+ rec.failures = [];
36
+ this.transition(providerId, rec, 'tripped', 'auth');
37
+ return;
38
+ }
39
+ rec.failures.push(now);
40
+ rec.failures = rec.failures.filter((t) => now - t <= this.windowMs);
41
+ if (rec.failures.length >= this.failureThreshold) {
42
+ rec.trippedAt = now;
43
+ rec.trippedUntil = now + this.cooldownMs;
44
+ rec.failures = [];
45
+ this.transition(providerId, rec, 'tripped', reason);
46
+ }
47
+ }
48
+ recordSuccess(providerId) {
49
+ const rec = this.getOrCreate(providerId);
50
+ rec.failures = [];
51
+ if (rec.currentState === 'tripped') {
52
+ rec.trippedAt = null;
53
+ rec.trippedUntil = 0;
54
+ this.transition(providerId, rec, 'healthy', 'recover');
55
+ }
56
+ }
57
+ /** Force a state-transition pass for all tracked providers. Useful when
58
+ * the caller wants to drive recoveries on a timer. */
59
+ tick(_nowHint) {
60
+ for (const id of this.records.keys()) {
61
+ void this.state(id);
62
+ }
63
+ }
64
+ onStateChange(fn) {
65
+ this.listeners.add(fn);
66
+ return () => this.listeners.delete(fn);
67
+ }
68
+ getOrCreate(providerId) {
69
+ let rec = this.records.get(providerId);
70
+ if (!rec) {
71
+ rec = {
72
+ failures: [],
73
+ trippedAt: null,
74
+ trippedUntil: 0,
75
+ currentState: 'healthy',
76
+ };
77
+ this.records.set(providerId, rec);
78
+ }
79
+ return rec;
80
+ }
81
+ transition(providerId, rec, to, reason) {
82
+ const from = rec.currentState;
83
+ if (from === to)
84
+ return;
85
+ rec.currentState = to;
86
+ for (const fn of this.listeners) {
87
+ try {
88
+ fn({ providerId, from, to, reason });
89
+ }
90
+ catch {
91
+ /* one bad listener must not poison the rest */
92
+ }
93
+ }
94
+ }
95
+ }
96
+ //# sourceMappingURL=CircuitBreaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CircuitBreaker.js","sourceRoot":"","sources":["../../src/voice-pipeline/CircuitBreaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @module voice-pipeline/HealthyProvider
3
+ *
4
+ * Provider-health trait adopted by every STT/TTS implementation so chains
5
+ * can prune, route, and circuit-break without peering into each provider's
6
+ * private state.
7
+ */
8
+ import type { HealthErrorClass } from './VoicePipelineError.js';
9
+ export interface ProviderCapabilities {
10
+ /** ISO language tags supported; ['*'] for any language. */
11
+ languages: string[];
12
+ /** True if this provider streams; false for batch-only. */
13
+ streaming: boolean;
14
+ /** Known max concurrent sessions the provider allows; Infinity if unlimited. */
15
+ maxConcurrent: number;
16
+ /** Relative cost bucket. 'cheap' < 'standard' < 'premium'. */
17
+ costTier: 'cheap' | 'standard' | 'premium';
18
+ /** Expected latency class for real-time viability. */
19
+ latencyClass: 'realtime' | 'near-realtime' | 'batch';
20
+ }
21
+ export interface HealthCheckResult {
22
+ ok: boolean;
23
+ latencyMs?: number;
24
+ error?: {
25
+ class: HealthErrorClass;
26
+ message: string;
27
+ };
28
+ }
29
+ export interface HealthyProvider {
30
+ /** Unique, stable provider id; same value used in VoicePipelineError.provider. */
31
+ readonly providerId: string;
32
+ /** Lower = tried first. Apps may override via constructor option. */
33
+ readonly priority: number;
34
+ readonly capabilities: ProviderCapabilities;
35
+ /**
36
+ * Lightweight probe (should complete in under 1 second). Must NOT consume
37
+ * billable audio or synthesis during a health check — it exists to verify
38
+ * authentication and reachability, not to measure quality.
39
+ */
40
+ healthCheck(): Promise<HealthCheckResult>;
41
+ }
42
+ export declare function defaultCapabilities(overrides?: Partial<ProviderCapabilities>): ProviderCapabilities;
43
+ export declare function supportsLanguage(caps: ProviderCapabilities, lang: string): boolean;
44
+ //# sourceMappingURL=HealthyProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HealthyProvider.d.ts","sourceRoot":"","sources":["../../src/voice-pipeline/HealthyProvider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IAC3C,sDAAsD;IACtD,YAAY,EAAE,UAAU,GAAG,eAAe,GAAG,OAAO,CAAC;CACtD;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED,MAAM,WAAW,eAAe;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,oBAAoB,CAAC;IAC5C;;;;OAIG;IACH,WAAW,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC3C;AAED,wBAAgB,mBAAmB,CACjC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,oBAAoB,CAStB;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAWlF"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module voice-pipeline/HealthyProvider
3
+ *
4
+ * Provider-health trait adopted by every STT/TTS implementation so chains
5
+ * can prune, route, and circuit-break without peering into each provider's
6
+ * private state.
7
+ */
8
+ export function defaultCapabilities(overrides = {}) {
9
+ return {
10
+ languages: ['*'],
11
+ streaming: true,
12
+ maxConcurrent: Infinity,
13
+ costTier: 'standard',
14
+ latencyClass: 'realtime',
15
+ ...overrides,
16
+ };
17
+ }
18
+ export function supportsLanguage(caps, lang) {
19
+ if (caps.languages.includes('*'))
20
+ return true;
21
+ const normalized = lang.toLowerCase();
22
+ return caps.languages.some((supported) => {
23
+ const s = supported.toLowerCase();
24
+ return (s === normalized ||
25
+ normalized.startsWith(s + '-') ||
26
+ s.startsWith(normalized + '-'));
27
+ });
28
+ }
29
+ //# sourceMappingURL=HealthyProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HealthyProvider.js","sourceRoot":"","sources":["../../src/voice-pipeline/HealthyProvider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAqCH,MAAM,UAAU,mBAAmB,CACjC,YAA2C,EAAE;IAE7C,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,CAAC;QAChB,SAAS,EAAE,IAAI;QACf,aAAa,EAAE,QAAQ;QACvB,QAAQ,EAAE,UAAU;QACpB,YAAY,EAAE,UAAU;QACxB,GAAG,SAAS;KACb,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAA0B,EAAE,IAAY;IACvE,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACtC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;QACvC,MAAM,CAAC,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QAClC,OAAO,CACL,CAAC,KAAK,UAAU;YAChB,UAAU,CAAC,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC;YAC9B,CAAC,CAAC,UAAU,CAAC,UAAU,GAAG,GAAG,CAAC,CAC/B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @module voice-pipeline/TranscriptDedupe
3
+ *
4
+ * Duplicate-transcript detector used by StreamingSTTChain after a mid-
5
+ * utterance failover. The backup provider replays the ring buffer and
6
+ * re-transcribes audio the primary already saw; without dedupe the session
7
+ * sees "hello world" twice.
8
+ *
9
+ * Dedupe signal: audio-clock overlap (primary fact) + fuzzy string match
10
+ * (tie-breaker). Two transcripts overlap if their [audioStartMs, audioEndMs]
11
+ * ranges intersect; when they do, we compare normalized text.
12
+ *
13
+ * Same-provider observations are never considered duplicates — interim
14
+ * transcripts from a single streaming provider are part of its protocol.
15
+ */
16
+ export interface TranscriptObservation {
17
+ provider: string;
18
+ text: string;
19
+ audioStartMs: number;
20
+ audioEndMs: number;
21
+ isFinal: boolean;
22
+ }
23
+ export interface DedupeResult {
24
+ isDuplicate: boolean;
25
+ reason?: 'exact' | 'fuzzy' | 'superset';
26
+ against?: {
27
+ provider: string;
28
+ text: string;
29
+ };
30
+ }
31
+ export declare class TranscriptDedupe {
32
+ private recent;
33
+ private readonly fuzzyThreshold;
34
+ private readonly retainMs;
35
+ constructor(opts?: {
36
+ fuzzyThreshold?: number;
37
+ retainMs?: number;
38
+ });
39
+ evaluate(obs: TranscriptObservation): DedupeResult;
40
+ reset(): void;
41
+ private prune;
42
+ }
43
+ //# sourceMappingURL=TranscriptDedupe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TranscriptDedupe.d.ts","sourceRoot":"","sources":["../../src/voice-pipeline/TranscriptDedupe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;IACxC,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AA2BD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,IAAI,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO;IAKrE,QAAQ,CAAC,GAAG,EAAE,qBAAqB,GAAG,YAAY;IA+ClD,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,KAAK;CAId"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @module voice-pipeline/TranscriptDedupe
3
+ *
4
+ * Duplicate-transcript detector used by StreamingSTTChain after a mid-
5
+ * utterance failover. The backup provider replays the ring buffer and
6
+ * re-transcribes audio the primary already saw; without dedupe the session
7
+ * sees "hello world" twice.
8
+ *
9
+ * Dedupe signal: audio-clock overlap (primary fact) + fuzzy string match
10
+ * (tie-breaker). Two transcripts overlap if their [audioStartMs, audioEndMs]
11
+ * ranges intersect; when they do, we compare normalized text.
12
+ *
13
+ * Same-provider observations are never considered duplicates — interim
14
+ * transcripts from a single streaming provider are part of its protocol.
15
+ */
16
+ const PUNCT_RE = /[.,!?;:"'()\[\]{}]/g;
17
+ const WS_RE = /\s+/g;
18
+ function normalize(text) {
19
+ return text.toLowerCase().replace(PUNCT_RE, '').replace(WS_RE, ' ').trim();
20
+ }
21
+ function tokenSet(text) {
22
+ return new Set(normalize(text).split(' ').filter(Boolean));
23
+ }
24
+ function tokenSetSimilarity(a, b) {
25
+ const ta = tokenSet(a);
26
+ const tb = tokenSet(b);
27
+ if (ta.size === 0 && tb.size === 0)
28
+ return 1;
29
+ if (ta.size === 0 || tb.size === 0)
30
+ return 0;
31
+ let intersect = 0;
32
+ for (const t of ta)
33
+ if (tb.has(t))
34
+ intersect++;
35
+ return intersect / Math.max(ta.size, tb.size);
36
+ }
37
+ function rangesOverlap(a, b) {
38
+ return a.audioStartMs < b.audioEndMs && b.audioStartMs < a.audioEndMs;
39
+ }
40
+ export class TranscriptDedupe {
41
+ constructor(opts = {}) {
42
+ this.recent = [];
43
+ this.fuzzyThreshold = opts.fuzzyThreshold ?? 0.85;
44
+ this.retainMs = opts.retainMs ?? 10000;
45
+ }
46
+ evaluate(obs) {
47
+ this.prune(obs.audioEndMs);
48
+ for (const prev of this.recent) {
49
+ if (prev.provider === obs.provider)
50
+ continue;
51
+ if (!rangesOverlap(prev, obs))
52
+ continue;
53
+ const na = normalize(prev.text);
54
+ const nb = normalize(obs.text);
55
+ if (na === nb) {
56
+ this.recent.push(obs);
57
+ return {
58
+ isDuplicate: true,
59
+ reason: 'exact',
60
+ against: { provider: prev.provider, text: prev.text },
61
+ };
62
+ }
63
+ // Longer transcript subsumes shorter one (primary saw more audio by
64
+ // the time backup caught up — suppress the backup's shorter view).
65
+ if (na.includes(nb) || nb.includes(na)) {
66
+ if (na.length >= nb.length) {
67
+ this.recent.push(obs);
68
+ return {
69
+ isDuplicate: true,
70
+ reason: 'superset',
71
+ against: { provider: prev.provider, text: prev.text },
72
+ };
73
+ }
74
+ }
75
+ const sim = tokenSetSimilarity(prev.text, obs.text);
76
+ if (sim >= this.fuzzyThreshold) {
77
+ this.recent.push(obs);
78
+ return {
79
+ isDuplicate: true,
80
+ reason: 'fuzzy',
81
+ against: { provider: prev.provider, text: prev.text },
82
+ };
83
+ }
84
+ }
85
+ this.recent.push(obs);
86
+ return { isDuplicate: false };
87
+ }
88
+ reset() {
89
+ this.recent = [];
90
+ }
91
+ prune(upToMs) {
92
+ const cutoff = upToMs - this.retainMs;
93
+ this.recent = this.recent.filter((o) => o.audioEndMs >= cutoff);
94
+ }
95
+ }
96
+ //# sourceMappingURL=TranscriptDedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TranscriptDedupe.js","sourceRoot":"","sources":["../../src/voice-pipeline/TranscriptDedupe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @module voice-pipeline/VoiceMetricsReporter
3
+ *
4
+ * Typed pub/sub bus for voice-pipeline lifecycle events. Chains and
5
+ * circuit breakers emit structured events here; host applications
6
+ * subscribe to forward them to clients (WebSocket frames), metrics
7
+ * systems (Prometheus, Datadog), or logs.
8
+ *
9
+ * Listener errors are swallowed — one bad subscriber must not poison the
10
+ * fan-out path for others.
11
+ */
12
+ import type { HealthErrorClass } from './VoicePipelineError.js';
13
+ export type VoiceMetricEvent = {
14
+ type: 'provider_selected';
15
+ kind: 'stt' | 'tts';
16
+ providerId: string;
17
+ attempt: number;
18
+ } | {
19
+ type: 'provider_failed';
20
+ kind: 'stt' | 'tts';
21
+ providerId: string;
22
+ errorClass: HealthErrorClass;
23
+ message: string;
24
+ } | {
25
+ type: 'provider_failover';
26
+ kind: 'stt' | 'tts';
27
+ from: string;
28
+ to: string;
29
+ reason: HealthErrorClass;
30
+ lostMs: number;
31
+ } | {
32
+ type: 'provider_degraded';
33
+ kind: 'stt' | 'tts';
34
+ providerId: string;
35
+ latencyMs: number;
36
+ thresholdMs: number;
37
+ } | {
38
+ type: 'provider_unavailable';
39
+ kind: 'stt' | 'tts';
40
+ checkedProviders: string[];
41
+ };
42
+ export type VoiceMetricListener = (event: VoiceMetricEvent) => void;
43
+ export declare class VoiceMetricsReporter {
44
+ private readonly listeners;
45
+ subscribe(fn: VoiceMetricListener): () => void;
46
+ emit(event: VoiceMetricEvent): void;
47
+ }
48
+ //# sourceMappingURL=VoiceMetricsReporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceMetricsReporter.d.ts","sourceRoot":"","sources":["../../src/voice-pipeline/VoiceMetricsReporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB,GACD;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB,GACD;IACE,IAAI,EAAE,sBAAsB,CAAC;IAC7B,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IACpB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEN,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,CAAC;AAEpE,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAE5D,SAAS,CAAC,EAAE,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAO9C,IAAI,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;CASpC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @module voice-pipeline/VoiceMetricsReporter
3
+ *
4
+ * Typed pub/sub bus for voice-pipeline lifecycle events. Chains and
5
+ * circuit breakers emit structured events here; host applications
6
+ * subscribe to forward them to clients (WebSocket frames), metrics
7
+ * systems (Prometheus, Datadog), or logs.
8
+ *
9
+ * Listener errors are swallowed — one bad subscriber must not poison the
10
+ * fan-out path for others.
11
+ */
12
+ export class VoiceMetricsReporter {
13
+ constructor() {
14
+ this.listeners = new Set();
15
+ }
16
+ subscribe(fn) {
17
+ this.listeners.add(fn);
18
+ return () => {
19
+ this.listeners.delete(fn);
20
+ };
21
+ }
22
+ emit(event) {
23
+ for (const fn of this.listeners) {
24
+ try {
25
+ fn(event);
26
+ }
27
+ catch {
28
+ /* swallow — one bad listener must not poison the rest */
29
+ }
30
+ }
31
+ }
32
+ }
33
+ //# sourceMappingURL=VoiceMetricsReporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceMetricsReporter.js","sourceRoot":"","sources":["../../src/voice-pipeline/VoiceMetricsReporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAyCH,MAAM,OAAO,oBAAoB;IAAjC;QACmB,cAAS,GAAG,IAAI,GAAG,EAAuB,CAAC;IAkB9D,CAAC;IAhBC,SAAS,CAAC,EAAuB;QAC/B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAuB;QAC1B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,EAAE,CAAC,KAAK,CAAC,CAAC;YACZ,CAAC;YAAC,MAAM,CAAC;gBACP,yDAAyD;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @module voice-pipeline/VoicePipelineError
3
+ *
4
+ * Structured error class for voice pipeline failures. Carries enough shape
5
+ * for chains and circuit breakers to classify and react without stringly
6
+ * matching error.message.
7
+ */
8
+ export type HealthErrorClass = 'auth' | 'quota' | 'network' | 'service' | 'unknown';
9
+ export interface VoicePipelineErrorInit {
10
+ kind: 'stt' | 'tts' | 'transport';
11
+ provider: string;
12
+ errorClass: HealthErrorClass;
13
+ message: string;
14
+ cause?: unknown;
15
+ retryable: boolean;
16
+ }
17
+ export declare class VoicePipelineError extends Error {
18
+ readonly kind: VoicePipelineErrorInit['kind'];
19
+ readonly provider: string;
20
+ readonly errorClass: HealthErrorClass;
21
+ readonly retryable: boolean;
22
+ readonly cause?: unknown;
23
+ constructor(init: VoicePipelineErrorInit);
24
+ /**
25
+ * Best-effort classification of an arbitrary error into a voice-pipeline
26
+ * error with a well-known errorClass. Preserves the original error as
27
+ * `cause` so upstream inspection can still recover provider-specific
28
+ * detail.
29
+ */
30
+ static classifyError(err: unknown, meta: {
31
+ kind: VoicePipelineErrorInit['kind'];
32
+ provider: string;
33
+ }): VoicePipelineError;
34
+ }
35
+ /**
36
+ * Aggregate thrown by `StreamingSTTChain` / `StreamingTTSChain` when every
37
+ * candidate provider fails. Carries the per-provider error list so callers
38
+ * can display a breakdown rather than a single confusing message.
39
+ */
40
+ export declare class AggregateVoiceError extends Error {
41
+ readonly attempts: VoicePipelineError[];
42
+ constructor(attempts: VoicePipelineError[]);
43
+ }
44
+ //# sourceMappingURL=VoicePipelineError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoicePipelineError.d.ts","sourceRoot":"","sources":["../../src/voice-pipeline/VoicePipelineError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,OAAO,GACP,SAAS,GACT,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,WAAW,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC;IACtC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEb,IAAI,EAAE,sBAAsB;IAUxC;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAClB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;QAAE,IAAI,EAAE,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAC/D,kBAAkB;CAoCtB;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;gBAE5B,QAAQ,EAAE,kBAAkB,EAAE;CAQ3C"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @module voice-pipeline/VoicePipelineError
3
+ *
4
+ * Structured error class for voice pipeline failures. Carries enough shape
5
+ * for chains and circuit breakers to classify and react without stringly
6
+ * matching error.message.
7
+ */
8
+ export class VoicePipelineError extends Error {
9
+ constructor(init) {
10
+ super(init.message);
11
+ this.name = 'VoicePipelineError';
12
+ this.kind = init.kind;
13
+ this.provider = init.provider;
14
+ this.errorClass = init.errorClass;
15
+ this.retryable = init.retryable;
16
+ this.cause = init.cause;
17
+ }
18
+ /**
19
+ * Best-effort classification of an arbitrary error into a voice-pipeline
20
+ * error with a well-known errorClass. Preserves the original error as
21
+ * `cause` so upstream inspection can still recover provider-specific
22
+ * detail.
23
+ */
24
+ static classifyError(err, meta) {
25
+ const raw = err instanceof Error ? err : new Error(String(err));
26
+ const msg = raw.message ?? '';
27
+ const code = err?.code ?? '';
28
+ let errorClass = 'unknown';
29
+ let retryable = true;
30
+ if (/\b401\b|unauthori[sz]ed|invalid api key|forbidden|\b403\b/i.test(msg)) {
31
+ errorClass = 'auth';
32
+ retryable = false;
33
+ }
34
+ else if (/\b429\b|rate.?limit|too many/i.test(msg)) {
35
+ errorClass = 'quota';
36
+ retryable = true;
37
+ }
38
+ else if (/\b5\d\d\b|internal server|bad gateway|gateway timeout|service unavailable/i.test(msg)) {
39
+ errorClass = 'service';
40
+ retryable = true;
41
+ }
42
+ else if (code === 'ECONNRESET' ||
43
+ code === 'ETIMEDOUT' ||
44
+ code === 'ENOTFOUND' ||
45
+ /econnreset|etimedout|enotfound|socket hang up|network/i.test(msg)) {
46
+ errorClass = 'network';
47
+ retryable = true;
48
+ }
49
+ return new VoicePipelineError({
50
+ kind: meta.kind,
51
+ provider: meta.provider,
52
+ errorClass,
53
+ message: msg || 'unknown voice pipeline error',
54
+ cause: err,
55
+ retryable,
56
+ });
57
+ }
58
+ }
59
+ /**
60
+ * Aggregate thrown by `StreamingSTTChain` / `StreamingTTSChain` when every
61
+ * candidate provider fails. Carries the per-provider error list so callers
62
+ * can display a breakdown rather than a single confusing message.
63
+ */
64
+ export class AggregateVoiceError extends Error {
65
+ constructor(attempts) {
66
+ const summary = attempts
67
+ .map((a) => `${a.provider}: ${a.errorClass} \u2014 ${a.message}`)
68
+ .join('; ');
69
+ super(`All ${attempts.length} providers failed \u2014 ${summary}`);
70
+ this.name = 'AggregateVoiceError';
71
+ this.attempts = attempts;
72
+ }
73
+ }
74
+ //# sourceMappingURL=VoicePipelineError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoicePipelineError.js","sourceRoot":"","sources":["../../src/voice-pipeline/VoicePipelineError.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAkBH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAO3C,YAAY,IAA4B;QACtC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAC1B,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAClB,GAAY,EACZ,IAAgE;QAEhE,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAI,GAAgC,EAAE,IAAI,IAAI,EAAE,CAAC;QAE3D,IAAI,UAAU,GAAqB,SAAS,CAAC;QAC7C,IAAI,SAAS,GAAG,IAAI,CAAC;QAErB,IAAI,4DAA4D,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3E,UAAU,GAAG,MAAM,CAAC;YACpB,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;aAAM,IAAI,+BAA+B,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,UAAU,GAAG,OAAO,CAAC;YACrB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,IAAI,4EAA4E,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAClG,UAAU,GAAG,SAAS,CAAC;YACvB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,IACL,IAAI,KAAK,YAAY;YACrB,IAAI,KAAK,WAAW;YACpB,IAAI,KAAK,WAAW;YACpB,wDAAwD,CAAC,IAAI,CAAC,GAAG,CAAC,EAClE,CAAC;YACD,UAAU,GAAG,SAAS,CAAC;YACvB,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;QAED,OAAO,IAAI,kBAAkB,CAAC;YAC5B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,UAAU;YACV,OAAO,EAAE,GAAG,IAAI,8BAA8B;YAC9C,KAAK,EAAE,GAAG;YACV,SAAS;SACV,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAG5C,YAAY,QAA8B;QACxC,MAAM,OAAO,GAAG,QAAQ;aACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,UAAU,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC;aAChE,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,KAAK,CAAC,OAAO,QAAQ,CAAC,MAAM,4BAA4B,OAAO,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF"}