@kuralle-syrinx/core 4.4.0 → 4.5.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.
Files changed (217) hide show
  1. package/dist/audio/alaw.d.ts +3 -0
  2. package/dist/audio/alaw.d.ts.map +1 -0
  3. package/dist/audio/alaw.js +53 -0
  4. package/dist/audio/alaw.js.map +1 -0
  5. package/dist/audio/g722.d.ts +39 -0
  6. package/dist/audio/g722.d.ts.map +1 -0
  7. package/dist/audio/g722.js +286 -0
  8. package/dist/audio/g722.js.map +1 -0
  9. package/dist/audio/index.d.ts +7 -0
  10. package/dist/audio/index.d.ts.map +1 -0
  11. package/dist/audio/index.js +8 -0
  12. package/dist/audio/index.js.map +1 -0
  13. package/dist/audio/loudness.d.ts +13 -0
  14. package/dist/audio/loudness.d.ts.map +1 -0
  15. package/dist/audio/loudness.js +65 -0
  16. package/dist/audio/loudness.js.map +1 -0
  17. package/dist/audio/mulaw.d.ts +3 -0
  18. package/dist/audio/mulaw.d.ts.map +1 -0
  19. package/dist/audio/mulaw.js +39 -0
  20. package/dist/audio/mulaw.js.map +1 -0
  21. package/dist/audio/pcm.d.ts +5 -0
  22. package/dist/audio/pcm.d.ts.map +1 -0
  23. package/dist/audio/pcm.js +40 -0
  24. package/dist/audio/pcm.js.map +1 -0
  25. package/dist/audio/resample.d.ts +12 -0
  26. package/dist/audio/resample.d.ts.map +1 -0
  27. package/dist/audio/resample.js +157 -0
  28. package/dist/audio/resample.js.map +1 -0
  29. package/dist/audio-envelope.d.ts +23 -0
  30. package/dist/audio-envelope.d.ts.map +1 -0
  31. package/dist/audio-envelope.js +114 -0
  32. package/dist/audio-envelope.js.map +1 -0
  33. package/dist/confidence-to-wait.d.ts +12 -0
  34. package/dist/confidence-to-wait.d.ts.map +1 -0
  35. package/dist/confidence-to-wait.js +18 -0
  36. package/dist/confidence-to-wait.js.map +1 -0
  37. package/dist/conversation-event.d.ts +19 -0
  38. package/dist/conversation-event.d.ts.map +1 -0
  39. package/dist/conversation-event.js +44 -0
  40. package/dist/conversation-event.js.map +1 -0
  41. package/dist/error-handler.d.ts +35 -0
  42. package/dist/error-handler.d.ts.map +1 -0
  43. package/dist/error-handler.js +136 -0
  44. package/dist/error-handler.js.map +1 -0
  45. package/dist/idle-timeout.d.ts +69 -0
  46. package/dist/idle-timeout.d.ts.map +1 -0
  47. package/dist/idle-timeout.js +149 -0
  48. package/dist/idle-timeout.js.map +1 -0
  49. package/dist/incremental-unit.d.ts +17 -0
  50. package/dist/incremental-unit.d.ts.map +1 -0
  51. package/dist/incremental-unit.js +5 -0
  52. package/dist/incremental-unit.js.map +1 -0
  53. package/dist/index.d.ts +47 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +61 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/init-chain.d.ts +35 -0
  58. package/dist/init-chain.d.ts.map +1 -0
  59. package/dist/init-chain.js +102 -0
  60. package/dist/init-chain.js.map +1 -0
  61. package/dist/init-stage-order.d.ts +5 -0
  62. package/dist/init-stage-order.d.ts.map +1 -0
  63. package/dist/init-stage-order.js +74 -0
  64. package/dist/init-stage-order.js.map +1 -0
  65. package/dist/interaction-coordinator.d.ts +45 -0
  66. package/dist/interaction-coordinator.d.ts.map +1 -0
  67. package/dist/interaction-coordinator.js +197 -0
  68. package/dist/interaction-coordinator.js.map +1 -0
  69. package/dist/interaction-policy.d.ts +103 -0
  70. package/dist/interaction-policy.d.ts.map +1 -0
  71. package/dist/interaction-policy.js +6 -0
  72. package/dist/interaction-policy.js.map +1 -0
  73. package/dist/iu-ledger.d.ts +39 -0
  74. package/dist/iu-ledger.d.ts.map +1 -0
  75. package/dist/iu-ledger.js +90 -0
  76. package/dist/iu-ledger.js.map +1 -0
  77. package/dist/latency-filler-fixtures.d.ts +7 -0
  78. package/dist/latency-filler-fixtures.d.ts.map +1 -0
  79. package/dist/latency-filler-fixtures.js +10 -0
  80. package/dist/latency-filler-fixtures.js.map +1 -0
  81. package/dist/latency-filler.d.ts +29 -0
  82. package/dist/latency-filler.d.ts.map +1 -0
  83. package/dist/latency-filler.js +102 -0
  84. package/dist/latency-filler.js.map +1 -0
  85. package/dist/mode-switcher.d.ts +24 -0
  86. package/dist/mode-switcher.d.ts.map +1 -0
  87. package/dist/mode-switcher.js +83 -0
  88. package/dist/mode-switcher.js.map +1 -0
  89. package/dist/observability-observer.d.ts +36 -0
  90. package/dist/observability-observer.d.ts.map +1 -0
  91. package/dist/observability-observer.js +141 -0
  92. package/dist/observability-observer.js.map +1 -0
  93. package/dist/observability.d.ts +68 -0
  94. package/dist/observability.d.ts.map +1 -0
  95. package/dist/observability.js +70 -0
  96. package/dist/observability.js.map +1 -0
  97. package/dist/packet-factories.d.ts +58 -0
  98. package/dist/packet-factories.d.ts.map +1 -0
  99. package/dist/packet-factories.js +181 -0
  100. package/dist/packet-factories.js.map +1 -0
  101. package/dist/packets.d.ts +532 -0
  102. package/dist/packets.d.ts.map +1 -0
  103. package/dist/packets.js +51 -0
  104. package/dist/packets.js.map +1 -0
  105. package/dist/pipeline-bus.d.ts +92 -0
  106. package/dist/pipeline-bus.d.ts.map +1 -0
  107. package/dist/pipeline-bus.js +294 -0
  108. package/dist/pipeline-bus.js.map +1 -0
  109. package/dist/plugin-contract.d.ts +73 -0
  110. package/dist/plugin-contract.d.ts.map +1 -0
  111. package/dist/plugin-contract.js +30 -0
  112. package/dist/plugin-contract.js.map +1 -0
  113. package/dist/policies/defer.d.ts +11 -0
  114. package/dist/policies/defer.d.ts.map +1 -0
  115. package/dist/policies/defer.js +13 -0
  116. package/dist/policies/defer.js.map +1 -0
  117. package/dist/policies/rule-based.d.ts +35 -0
  118. package/dist/policies/rule-based.d.ts.map +1 -0
  119. package/dist/policies/rule-based.js +130 -0
  120. package/dist/policies/rule-based.js.map +1 -0
  121. package/dist/pricing.d.ts +34 -0
  122. package/dist/pricing.d.ts.map +1 -0
  123. package/dist/pricing.js +108 -0
  124. package/dist/pricing.js.map +1 -0
  125. package/dist/primary-speaker-fixtures.d.ts +12 -0
  126. package/dist/primary-speaker-fixtures.d.ts.map +1 -0
  127. package/dist/primary-speaker-fixtures.js +37 -0
  128. package/dist/primary-speaker-fixtures.js.map +1 -0
  129. package/dist/primary-speaker-gate.d.ts +42 -0
  130. package/dist/primary-speaker-gate.d.ts.map +1 -0
  131. package/dist/primary-speaker-gate.js +178 -0
  132. package/dist/primary-speaker-gate.js.map +1 -0
  133. package/dist/provider-fallback.d.ts +28 -0
  134. package/dist/provider-fallback.d.ts.map +1 -0
  135. package/dist/provider-fallback.js +72 -0
  136. package/dist/provider-fallback.js.map +1 -0
  137. package/dist/reasoner-hedge.d.ts +21 -0
  138. package/dist/reasoner-hedge.d.ts.map +1 -0
  139. package/dist/reasoner-hedge.js +170 -0
  140. package/dist/reasoner-hedge.js.map +1 -0
  141. package/dist/reasoner-route.d.ts +24 -0
  142. package/dist/reasoner-route.d.ts.map +1 -0
  143. package/dist/reasoner-route.js +87 -0
  144. package/dist/reasoner-route.js.map +1 -0
  145. package/dist/reasoner-session-store.d.ts +18 -0
  146. package/dist/reasoner-session-store.d.ts.map +1 -0
  147. package/dist/reasoner-session-store.js +21 -0
  148. package/dist/reasoner-session-store.js.map +1 -0
  149. package/dist/reasoner.d.ts +97 -0
  150. package/dist/reasoner.d.ts.map +1 -0
  151. package/dist/reasoner.js +9 -0
  152. package/dist/reasoner.js.map +1 -0
  153. package/dist/retry.d.ts +31 -0
  154. package/dist/retry.d.ts.map +1 -0
  155. package/dist/retry.js +83 -0
  156. package/dist/retry.js.map +1 -0
  157. package/dist/scheduler.d.ts +11 -0
  158. package/dist/scheduler.d.ts.map +1 -0
  159. package/dist/scheduler.js +20 -0
  160. package/dist/scheduler.js.map +1 -0
  161. package/dist/spend-cap.d.ts +27 -0
  162. package/dist/spend-cap.d.ts.map +1 -0
  163. package/dist/spend-cap.js +38 -0
  164. package/dist/spend-cap.js.map +1 -0
  165. package/dist/tts-playout-clock.d.ts +43 -0
  166. package/dist/tts-playout-clock.d.ts.map +1 -0
  167. package/dist/tts-playout-clock.js +125 -0
  168. package/dist/tts-playout-clock.js.map +1 -0
  169. package/dist/turn-arbiter.d.ts +41 -0
  170. package/dist/turn-arbiter.d.ts.map +1 -0
  171. package/dist/turn-arbiter.js +259 -0
  172. package/dist/turn-arbiter.js.map +1 -0
  173. package/dist/voice-agent-session-util.d.ts +60 -0
  174. package/dist/voice-agent-session-util.d.ts.map +1 -0
  175. package/dist/voice-agent-session-util.js +204 -0
  176. package/dist/voice-agent-session-util.js.map +1 -0
  177. package/dist/voice-agent-session.d.ts +435 -0
  178. package/dist/voice-agent-session.d.ts.map +1 -0
  179. package/dist/voice-agent-session.js +1597 -0
  180. package/dist/voice-agent-session.js.map +1 -0
  181. package/dist/voice-text.d.ts +46 -0
  182. package/dist/voice-text.d.ts.map +1 -0
  183. package/dist/voice-text.js +209 -0
  184. package/dist/voice-text.js.map +1 -0
  185. package/package.json +19 -6
  186. package/src/audio/alaw.test.ts +87 -0
  187. package/src/audio/audio.test.ts +285 -0
  188. package/src/audio/g722.test.ts +149 -0
  189. package/src/audio/loudness.test.ts +58 -0
  190. package/src/audio-envelope.test.ts +167 -0
  191. package/src/confidence-to-wait.test.ts +21 -0
  192. package/src/error-handler.test.ts +56 -0
  193. package/src/hedge-throwing-backend.test.ts +46 -0
  194. package/src/interaction-coordinator.test.ts +529 -0
  195. package/src/iu-ledger.test.ts +276 -0
  196. package/src/latency-filler.test.ts +62 -0
  197. package/src/observability-observer.test.ts +248 -0
  198. package/src/observability.test.ts +94 -0
  199. package/src/packet-factories.test.ts +126 -0
  200. package/src/pipeline-bus.g10.test.ts +145 -0
  201. package/src/pipeline-bus.test.ts +211 -0
  202. package/src/policies/defer.test.ts +86 -0
  203. package/src/policies/rule-based.test.ts +334 -0
  204. package/src/pricing.test.ts +226 -0
  205. package/src/primary-speaker-gate.test.ts +150 -0
  206. package/src/provider-fallback.test.ts +87 -0
  207. package/src/reasoner-hedge.test.ts +361 -0
  208. package/src/reasoner-route.test.ts +248 -0
  209. package/src/reasoner.test.ts +69 -0
  210. package/src/retry.test.ts +83 -0
  211. package/src/route-throwing-spec.test.ts +44 -0
  212. package/src/spend-cap.test.ts +114 -0
  213. package/src/tts-playout-clock.test.ts +125 -0
  214. package/src/turn-arbiter.characterization.test.ts +477 -0
  215. package/src/turn-arbiter.test.ts +762 -0
  216. package/src/voice-agent-session.test.ts +4165 -0
  217. package/src/voice-text.test.ts +156 -0
@@ -0,0 +1,130 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import { TurnArbiter } from "../turn-arbiter.js";
3
+ export class RuleBasedInteractionPolicy {
4
+ arbiter;
5
+ ttsPlayout;
6
+ backchannelConfig;
7
+ pending = [];
8
+ bargeInAudioConsumed = false;
9
+ delegateGapOpen = false;
10
+ delegateCuePlayed = false;
11
+ userSpeaking = false;
12
+ constructor(deps) {
13
+ this.ttsPlayout = deps.ttsPlayout;
14
+ this.backchannelConfig = deps.backchannel;
15
+ this.arbiter = new TurnArbiter({
16
+ bus: deps.bus,
17
+ primarySpeakerGate: deps.primarySpeakerGate,
18
+ ttsPlayout: deps.ttsPlayout,
19
+ minInterruptionMs: deps.minInterruptionMs,
20
+ pauseThenResolveBargeIn: deps.pauseThenResolveBargeIn,
21
+ onInterrupt: (id) => this.pending.push({ kind: "interrupt", interruptedContextId: id }),
22
+ });
23
+ }
24
+ observe(obs) {
25
+ switch (obs.kind) {
26
+ case "vad_speech_started":
27
+ this.userSpeaking = true;
28
+ if (obs.interruptedContextId)
29
+ this.arbiter.onSpeechStarted({
30
+ kind: "vad.speech_started",
31
+ contextId: obs.contextId,
32
+ timestampMs: obs.timestampMs,
33
+ confidence: obs.confidence,
34
+ }, obs.interruptedContextId);
35
+ break;
36
+ case "vad_speech_activity":
37
+ this.arbiter.onSpeechActivity({
38
+ kind: "vad.speech_activity",
39
+ contextId: obs.contextId,
40
+ timestampMs: obs.timestampMs,
41
+ isAsync: true,
42
+ });
43
+ break;
44
+ case "vad_speech_ended":
45
+ this.userSpeaking = false;
46
+ this.arbiter.onSpeechEnded({
47
+ kind: "vad.speech_ended",
48
+ contextId: obs.contextId,
49
+ timestampMs: obs.timestampMs,
50
+ }, obs.hasActiveTts);
51
+ break;
52
+ case "vad_barge_in_audio":
53
+ this.bargeInAudioConsumed = this.arbiter.observeBargeInAudio({
54
+ kind: "vad.audio",
55
+ contextId: obs.contextId,
56
+ timestampMs: obs.timestampMs,
57
+ audio: obs.audio,
58
+ });
59
+ break;
60
+ case "stt_partial":
61
+ this.arbiter.noteInterimEvidence(obs.text);
62
+ if (obs.interruptedContextId) {
63
+ this.arbiter.onProviderSttEvidence(obs.contextId, obs.timestampMs, obs.interruptedContextId);
64
+ }
65
+ break;
66
+ case "stt_final":
67
+ this.arbiter.noteInterimEvidence(obs.text, obs.confidence);
68
+ if (obs.interruptedContextId) {
69
+ this.arbiter.onProviderSttEvidence(obs.contextId, obs.timestampMs, obs.interruptedContextId);
70
+ }
71
+ break;
72
+ case "delegate_state":
73
+ this.pending.push(...this.handleDelegateState(obs));
74
+ break;
75
+ default:
76
+ break;
77
+ }
78
+ if (this.pending.length === 0)
79
+ return [];
80
+ const out = this.pending;
81
+ this.pending = [];
82
+ return out;
83
+ }
84
+ handleDelegateState(obs) {
85
+ const phase = obs.toolCallPhase;
86
+ if (!phase)
87
+ return [];
88
+ switch (phase) {
89
+ case "started":
90
+ this.delegateGapOpen = true;
91
+ this.delegateCuePlayed = false;
92
+ return [];
93
+ case "delayed": {
94
+ if (!this.delegateGapOpen || this.delegateCuePlayed)
95
+ return [];
96
+ if (this.depsTtsActive())
97
+ return [];
98
+ if (this.userSpeaking)
99
+ return [];
100
+ this.delegateCuePlayed = true;
101
+ if (this.backchannelConfig?.enabled === false)
102
+ return [];
103
+ const cue = this.backchannelConfig?.cues?.[0] ?? "mm_hmm";
104
+ return [{ kind: "backchannel", cue }];
105
+ }
106
+ case "complete":
107
+ case "failed":
108
+ this.delegateGapOpen = false;
109
+ this.delegateCuePlayed = false;
110
+ return [];
111
+ default:
112
+ return [];
113
+ }
114
+ }
115
+ depsTtsActive() {
116
+ return this.ttsPlayout.activeContexts().length > 0;
117
+ }
118
+ reset(_contextId) {
119
+ this.delegateGapOpen = false;
120
+ this.delegateCuePlayed = false;
121
+ this.userSpeaking = false;
122
+ this.arbiter.clear();
123
+ }
124
+ takeBargeInAudioConsumed() {
125
+ const consumed = this.bargeInAudioConsumed;
126
+ this.bargeInAudioConsumed = false;
127
+ return consumed;
128
+ }
129
+ }
130
+ //# sourceMappingURL=rule-based.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-based.js","sourceRoot":"","sources":["../../src/policies/rule-based.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { UsageRecordedPacket } from "./packets.js";
2
+ export interface SttPrice {
3
+ readonly usdPerAudioSecond: number;
4
+ }
5
+ export interface LlmPrice {
6
+ readonly usdPer1MInputTokens: number;
7
+ readonly usdPer1MOutputTokens: number;
8
+ readonly usdPer1MCachedInputTokens?: number;
9
+ }
10
+ export interface TtsPrice {
11
+ readonly usdPer1MCharacters: number;
12
+ }
13
+ export interface PriceCatalog {
14
+ readonly source: string;
15
+ readonly version: string;
16
+ /** Keys: `provider/model` (e.g. `deepgram/nova-3`). */
17
+ readonly stt: Readonly<Record<string, SttPrice>>;
18
+ readonly llm: Readonly<Record<string, LlmPrice>>;
19
+ readonly tts: Readonly<Record<string, TtsPrice>>;
20
+ }
21
+ export type CostResult = {
22
+ readonly usd: number;
23
+ readonly unpriced?: undefined;
24
+ } | {
25
+ readonly usd: null;
26
+ readonly unpriced: true;
27
+ };
28
+ /**
29
+ * Public list prices stamped into DEFAULT_PRICE_CATALOG.source.
30
+ * STT $/audio-second from $/min ÷ 60. TTS is $/1M characters. LLM is $/1M tokens.
31
+ */
32
+ export declare const DEFAULT_PRICE_CATALOG: PriceCatalog;
33
+ export declare function costOf(usage: UsageRecordedPacket, catalog: PriceCatalog): CostResult;
34
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,CAAC;CAC7C;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjD,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAOpD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAuDnC,CAAC;AAEF,wBAAgB,MAAM,CAAC,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,YAAY,GAAG,UAAU,CAmCpF"}
@@ -0,0 +1,108 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Versioned price catalog: usage.recorded quantities → USD.
4
+ // Unknown provider/model keys yield unpriced (never silent 0 for real providers).
5
+ // Declared local/self-hosted models are explicitly zero-cost.
6
+ function catalogKey(provider, model) {
7
+ if (!provider || !model)
8
+ return null;
9
+ return `${provider}/${model}`;
10
+ }
11
+ /**
12
+ * Public list prices stamped into DEFAULT_PRICE_CATALOG.source.
13
+ * STT $/audio-second from $/min ÷ 60. TTS is $/1M characters. LLM is $/1M tokens.
14
+ */
15
+ export const DEFAULT_PRICE_CATALOG = {
16
+ source: "voice-prices@3 | deepgram.com/pricing PAYG Nova-3 streaming monolingual $0.0077/min → $0.000128333/s; " +
17
+ "deepgram.com/pricing Aura-2 TTS $0.030/1k chars → $30/1M; " +
18
+ "cartesia.ai/pricing + docs.cartesia.ai/pricing ~1 credit/char, PAYG ~$50/1M chars (cloudtalk.io cartesia-pricing 2026); " +
19
+ "openai tts-1 $15/1M chars (openai.com api pricing); gpt-4o-mini-tts text $0.60/1M (community/azure listed text input rate); " +
20
+ "openai GPT-4.1-mini $0.40/$1.60 per 1M in/out (developers.openai.com/api/docs/pricing; Azure GPT-4.1-mini-2025-04-14 Global); " +
21
+ "cloud.google.com/speech-to-text/pricing V2 standard recognition $0.016/min → $0.000266667/s (latest_long); " +
22
+ "ai.google.dev/pricing Gemini 2.5 Flash TTS preview text $0.50/1M chars (Gemini API TTS text input rate, 2026); " +
23
+ "elevenlabs.io/pricing/api Flash/Turbo TTS $0.05/1k chars → $50/1M; Multilingual v2/v3 $0.10/1k → $100/1M; " +
24
+ "Scribe v2 Realtime $0.39/hour → $0.000108333/s; " +
25
+ "grok/stt + grok/* TTS + epsilon/epsilon-tts intentionally ABSENT (no public list price → costOf unpriced, never silent $0)",
26
+ version: "3",
27
+ stt: {
28
+ "deepgram/nova-3": { usdPerAudioSecond: 0.0077 / 60 },
29
+ "deepgram/nova-2": { usdPerAudioSecond: 0.0077 / 60 },
30
+ "deepgram/flux-general-en": { usdPerAudioSecond: 0.0077 / 60 },
31
+ // Google Cloud Speech-to-Text V2 standard streaming recognition list rate.
32
+ "google/latest_long": { usdPerAudioSecond: 0.016 / 60 },
33
+ "google/latest_short": { usdPerAudioSecond: 0.016 / 60 },
34
+ "google/chirp_2": { usdPerAudioSecond: 0.016 / 60 },
35
+ // ElevenLabs Scribe v2 Realtime — public list $0.39/hour.
36
+ "elevenlabs/scribe_v2_realtime": { usdPerAudioSecond: 0.39 / 3600 },
37
+ "local/whisper": { usdPerAudioSecond: 0 },
38
+ // grok/stt: no public list price — omit so costOf → unpriced
39
+ },
40
+ llm: {
41
+ "openai/gpt-4.1-mini": {
42
+ usdPer1MInputTokens: 0.4,
43
+ usdPer1MOutputTokens: 1.6,
44
+ usdPer1MCachedInputTokens: 0.1,
45
+ },
46
+ "local/llm": {
47
+ usdPer1MInputTokens: 0,
48
+ usdPer1MOutputTokens: 0,
49
+ },
50
+ },
51
+ tts: {
52
+ "cartesia/sonic-3": { usdPer1MCharacters: 50 },
53
+ "openai/gpt-4o-mini-tts": { usdPer1MCharacters: 0.6 },
54
+ "openai/tts-1": { usdPer1MCharacters: 15 },
55
+ "deepgram/aura-2-thalia-en": { usdPer1MCharacters: 30 },
56
+ // Gemini TTS preview models — Gemini API TTS text input list rate.
57
+ "gemini/gemini-3.1-flash-tts-preview": { usdPer1MCharacters: 0.5 },
58
+ "gemini/gemini-2.5-flash-preview-tts": { usdPer1MCharacters: 0.5 },
59
+ "gemini/gemini-2.5-pro-preview-tts": { usdPer1MCharacters: 0.5 },
60
+ // ElevenLabs public list — Flash/Turbo $0.05/1k, Multilingual $0.10/1k.
61
+ "elevenlabs/eleven_flash_v2_5": { usdPer1MCharacters: 50 },
62
+ "elevenlabs/eleven_flash_v2": { usdPer1MCharacters: 50 },
63
+ "elevenlabs/eleven_turbo_v2_5": { usdPer1MCharacters: 50 },
64
+ "elevenlabs/eleven_turbo_v2": { usdPer1MCharacters: 50 },
65
+ "elevenlabs/eleven_multilingual_v2": { usdPer1MCharacters: 100 },
66
+ "local/tts": { usdPer1MCharacters: 0 },
67
+ // grok/* voice keys + epsilon/epsilon-tts: no public list — omit → unpriced
68
+ },
69
+ };
70
+ export function costOf(usage, catalog) {
71
+ const key = catalogKey(usage.provider, usage.model);
72
+ if (!key)
73
+ return { usd: null, unpriced: true };
74
+ switch (usage.stage) {
75
+ case "stt": {
76
+ const price = catalog.stt[key];
77
+ if (!price)
78
+ return { usd: null, unpriced: true };
79
+ const seconds = usage.audioSeconds ?? 0;
80
+ return { usd: seconds * price.usdPerAudioSecond };
81
+ }
82
+ case "tts": {
83
+ const price = catalog.tts[key];
84
+ if (!price)
85
+ return { usd: null, unpriced: true };
86
+ const characters = usage.characters ?? 0;
87
+ return { usd: (characters / 1_000_000) * price.usdPer1MCharacters };
88
+ }
89
+ case "llm": {
90
+ const price = catalog.llm[key];
91
+ if (!price)
92
+ return { usd: null, unpriced: true };
93
+ const input = usage.inputTokens ?? 0;
94
+ const output = usage.outputTokens ?? 0;
95
+ const cached = usage.cachedInputTokens ?? 0;
96
+ const cachedRate = price.usdPer1MCachedInputTokens ?? price.usdPer1MInputTokens;
97
+ // Prefer charging cached tokens at the cached rate when present; non-cached input is the remainder.
98
+ const nonCachedInput = Math.max(0, input - cached);
99
+ const usd = (nonCachedInput / 1_000_000) * price.usdPer1MInputTokens +
100
+ (cached / 1_000_000) * cachedRate +
101
+ (output / 1_000_000) * price.usdPer1MOutputTokens;
102
+ return { usd };
103
+ }
104
+ default:
105
+ return { usd: null, unpriced: true };
106
+ }
107
+ }
108
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export declare function synthesizeTonePcm16(options: {
2
+ frequencyHz: number;
3
+ durationMs: number;
4
+ sampleRateHz?: number;
5
+ amplitude?: number;
6
+ phaseRad?: number;
7
+ }): Uint8Array;
8
+ export declare function mixPcm16(chunks: Uint8Array[], weights: number[]): Uint8Array;
9
+ export declare const PRIMARY_SPEAKER_TONE_HZ = 280;
10
+ export declare const BYSTANDER_SPEAKER_TONE_HZ = 2100;
11
+ export declare const ASSISTANT_ECHO_TONE_HZ = 520;
12
+ //# sourceMappingURL=primary-speaker-fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-speaker-fixtures.d.ts","sourceRoot":"","sources":["../src/primary-speaker-fixtures.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,UAAU,CAYb;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,CAc5E;AAED,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C,eAAO,MAAM,sBAAsB,MAAM,CAAC"}
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Synthetic PCM fixtures for primary-speaker gate tests.
4
+ import { pcm16BytesToSamples, pcm16SamplesToBytes } from "./audio/pcm.js";
5
+ export function synthesizeTonePcm16(options) {
6
+ const sampleRateHz = options.sampleRateHz ?? 16000;
7
+ const amplitude = options.amplitude ?? 0.35;
8
+ const phaseRad = options.phaseRad ?? 0;
9
+ const sampleCount = Math.max(1, Math.round((options.durationMs * sampleRateHz) / 1000));
10
+ const samples = new Int16Array(sampleCount);
11
+ const omega = (2 * Math.PI * options.frequencyHz) / sampleRateHz;
12
+ for (let i = 0; i < sampleCount; i += 1) {
13
+ const value = Math.sin(omega * i + phaseRad) * amplitude * 32767;
14
+ samples[i] = Math.max(-32768, Math.min(32767, Math.round(value)));
15
+ }
16
+ return pcm16SamplesToBytes(samples);
17
+ }
18
+ export function mixPcm16(chunks, weights) {
19
+ if (chunks.length === 0)
20
+ return new Uint8Array(0);
21
+ const maxLen = Math.max(...chunks.map((c) => c.byteLength));
22
+ const out = new Int16Array(maxLen / 2);
23
+ for (let c = 0; c < chunks.length; c += 1) {
24
+ const chunk = chunks[c];
25
+ const weight = weights[c] ?? 1;
26
+ const samples = pcm16BytesToSamples(chunk);
27
+ for (let i = 0; i < samples.length; i += 1) {
28
+ const mixed = (out[i] ?? 0) + samples[i] * weight;
29
+ out[i] = Math.max(-32768, Math.min(32767, Math.round(mixed)));
30
+ }
31
+ }
32
+ return pcm16SamplesToBytes(out);
33
+ }
34
+ export const PRIMARY_SPEAKER_TONE_HZ = 280;
35
+ export const BYSTANDER_SPEAKER_TONE_HZ = 2100;
36
+ export const ASSISTANT_ECHO_TONE_HZ = 520;
37
+ //# sourceMappingURL=primary-speaker-fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-speaker-fixtures.js","sourceRoot":"","sources":["../src/primary-speaker-fixtures.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,yDAAyD;AAEzD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1E,MAAM,UAAU,mBAAmB,CAAC,OAMnC;IACC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACxF,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,YAAY,CAAC;IACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC;QACjE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAoB,EAAE,OAAiB;IAC9D,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACzB,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3C,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC;YACnD,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IACD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAC9C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,42 @@
1
+ export interface SpeakerFingerprint {
2
+ readonly bands: readonly number[];
3
+ readonly rms: number;
4
+ readonly zcr: number;
5
+ }
6
+ export interface PrimarySpeakerGateConfig {
7
+ readonly enabled?: boolean;
8
+ readonly similarityThreshold?: number;
9
+ readonly echoDominanceMargin?: number;
10
+ readonly onDecision?: (decision: PrimarySpeakerGateDecision) => void;
11
+ }
12
+ export interface PrimarySpeakerGateDecision {
13
+ readonly contextId: string;
14
+ readonly signal: "primary_speaker" | "echo_rejected";
15
+ readonly accepted: boolean;
16
+ readonly frameCount: number;
17
+ readonly primaryHits: number;
18
+ readonly echoRejectedFrames: number;
19
+ }
20
+ export declare class PrimarySpeakerGate {
21
+ private readonly enabled;
22
+ private readonly similarityThreshold;
23
+ private readonly echoDominanceMargin;
24
+ private readonly onDecision?;
25
+ private profile;
26
+ private enrollFrames;
27
+ private bargeInFrames;
28
+ private assistantProfile;
29
+ constructor(config?: PrimarySpeakerGateConfig);
30
+ isEnabled(): boolean;
31
+ hasProfile(): boolean;
32
+ enrollUserTurnChunk(pcm: Uint8Array): void;
33
+ lockProfileFromFirstTurn(): void;
34
+ beginBargeInWindow(): void;
35
+ observeBargeInChunk(pcm: Uint8Array): void;
36
+ observeAssistantPlayout(pcm: Uint8Array): void;
37
+ shouldCommitBargeIn(contextId?: string): boolean;
38
+ resetBargeInWindow(): void;
39
+ }
40
+ export declare function extractSpeakerFingerprint(pcm: Uint8Array): SpeakerFingerprint | null;
41
+ export declare function fingerprintSimilarity(a: SpeakerFingerprint, b: SpeakerFingerprint): number;
42
+ //# sourceMappingURL=primary-speaker-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-speaker-gate.d.ts","sourceRoot":"","sources":["../src/primary-speaker-gate.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,0BAA0B,KAAK,IAAI,CAAC;CACtE;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAyC;IACrE,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,aAAa,CAA4B;IACjD,OAAO,CAAC,gBAAgB,CAAmC;gBAE/C,MAAM,GAAE,wBAA6B;IAOjD,SAAS,IAAI,OAAO;IAIpB,UAAU,IAAI,OAAO;IAIrB,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAM1C,wBAAwB,IAAI,IAAI;IAMhC,kBAAkB,IAAI,IAAI;IAI1B,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAM1C,uBAAuB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI;IAO9C,mBAAmB,CAAC,SAAS,SAAK,GAAG,OAAO;IA0B5C,kBAAkB,IAAI,IAAI;CAG3B;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,UAAU,GAAG,kBAAkB,GAAG,IAAI,CA6BpF;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE,kBAAkB,GAAG,MAAM,CAmB1F"}
@@ -0,0 +1,178 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // VE-02 / G23 — lightweight primary-speaker gate for barge-in (FireRedChat pVAD direction).
4
+ // Locks a spectral fingerprint from the first user turn; gates sustained barge-in on match.
5
+ import { pcm16BytesToSamples } from "./audio/pcm.js";
6
+ const SAMPLE_RATE_HZ = 16000;
7
+ const MIN_SAMPLES = 320;
8
+ const BAND_HZ = [150, 300, 600, 1200, 2400, 4800];
9
+ export class PrimarySpeakerGate {
10
+ enabled;
11
+ similarityThreshold;
12
+ echoDominanceMargin;
13
+ onDecision;
14
+ profile = null;
15
+ enrollFrames = [];
16
+ bargeInFrames = [];
17
+ assistantProfile = null;
18
+ constructor(config = {}) {
19
+ this.enabled = config.enabled !== false;
20
+ this.similarityThreshold = config.similarityThreshold ?? 0.72;
21
+ this.echoDominanceMargin = config.echoDominanceMargin ?? 0.12;
22
+ this.onDecision = config.onDecision;
23
+ }
24
+ isEnabled() {
25
+ return this.enabled;
26
+ }
27
+ hasProfile() {
28
+ return this.profile !== null;
29
+ }
30
+ enrollUserTurnChunk(pcm) {
31
+ if (!this.enabled || this.profile !== null)
32
+ return;
33
+ const frame = extractSpeakerFingerprint(pcm);
34
+ if (frame)
35
+ this.enrollFrames.push(frame);
36
+ }
37
+ lockProfileFromFirstTurn() {
38
+ if (!this.enabled || this.profile !== null || this.enrollFrames.length === 0)
39
+ return;
40
+ this.profile = averageFingerprints(this.enrollFrames);
41
+ this.enrollFrames = [];
42
+ }
43
+ beginBargeInWindow() {
44
+ this.bargeInFrames = [];
45
+ }
46
+ observeBargeInChunk(pcm) {
47
+ if (!this.enabled)
48
+ return;
49
+ const frame = extractSpeakerFingerprint(pcm);
50
+ if (frame)
51
+ this.bargeInFrames.push(frame);
52
+ }
53
+ observeAssistantPlayout(pcm) {
54
+ if (!this.enabled)
55
+ return;
56
+ const frame = extractSpeakerFingerprint(pcm);
57
+ if (!frame)
58
+ return;
59
+ this.assistantProfile = frame;
60
+ }
61
+ shouldCommitBargeIn(contextId = "") {
62
+ if (!this.enabled || this.profile === null)
63
+ return true;
64
+ if (this.bargeInFrames.length === 0)
65
+ return false;
66
+ let primaryHits = 0;
67
+ let echoRejectedFrames = 0;
68
+ for (const frame of this.bargeInFrames) {
69
+ const primarySim = fingerprintSimilarity(frame, this.profile);
70
+ if (this.assistantProfile !== null) {
71
+ const echoSim = fingerprintSimilarity(frame, this.assistantProfile);
72
+ if (echoSim >= primarySim + this.echoDominanceMargin) {
73
+ echoRejectedFrames += 1;
74
+ continue;
75
+ }
76
+ }
77
+ if (primarySim >= this.similarityThreshold)
78
+ primaryHits += 1;
79
+ }
80
+ const required = Math.max(1, Math.ceil(this.bargeInFrames.length * 0.45));
81
+ const accepted = primaryHits >= required;
82
+ const decision = { contextId, accepted, frameCount: this.bargeInFrames.length, primaryHits, echoRejectedFrames };
83
+ if (echoRejectedFrames > 0)
84
+ this.onDecision?.({ signal: "echo_rejected", ...decision });
85
+ this.onDecision?.({ signal: "primary_speaker", ...decision });
86
+ return accepted;
87
+ }
88
+ resetBargeInWindow() {
89
+ this.bargeInFrames = [];
90
+ }
91
+ }
92
+ export function extractSpeakerFingerprint(pcm) {
93
+ if (pcm.byteLength < MIN_SAMPLES * 2)
94
+ return null;
95
+ let samples;
96
+ try {
97
+ samples = pcm16BytesToSamples(pcm);
98
+ }
99
+ catch {
100
+ return null;
101
+ }
102
+ if (samples.length < MIN_SAMPLES)
103
+ return null;
104
+ const window = samples.length > 512 ? samples.subarray(0, 512) : samples;
105
+ const bands = BAND_HZ.map((hz) => goertzelMagnitude(window, hz, SAMPLE_RATE_HZ));
106
+ const maxBand = Math.max(...bands, 1e-9);
107
+ const normalizedBands = bands.map((b) => b / maxBand);
108
+ let sumSq = 0;
109
+ let crossings = 0;
110
+ let prev = window[0] >= 0;
111
+ for (let i = 0; i < window.length; i += 1) {
112
+ const s = window[i];
113
+ sumSq += s * s;
114
+ const positive = s >= 0;
115
+ if (i > 0 && positive !== prev)
116
+ crossings += 1;
117
+ prev = positive;
118
+ }
119
+ const rms = Math.sqrt(sumSq / window.length) / 32768;
120
+ const zcr = crossings / window.length;
121
+ return { bands: normalizedBands, rms, zcr };
122
+ }
123
+ export function fingerprintSimilarity(a, b) {
124
+ let dot = 0;
125
+ let normA = 0;
126
+ let normB = 0;
127
+ for (let i = 0; i < a.bands.length; i += 1) {
128
+ const av = a.bands[i] ?? 0;
129
+ const bv = b.bands[i] ?? 0;
130
+ dot += av * bv;
131
+ normA += av * av;
132
+ normB += bv * bv;
133
+ }
134
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
135
+ if (denom <= 0)
136
+ return 0;
137
+ const spectral = dot / denom;
138
+ const rmsDelta = Math.abs(a.rms - b.rms);
139
+ const zcrDelta = Math.abs(a.zcr - b.zcr);
140
+ const timbrePenalty = Math.min(1, rmsDelta * 4 + zcrDelta * 2);
141
+ return Math.max(0, spectral * (1 - timbrePenalty * 0.35));
142
+ }
143
+ function averageFingerprints(frames) {
144
+ const bandCount = frames[0].bands.length;
145
+ const bands = new Array(bandCount).fill(0);
146
+ let rms = 0;
147
+ let zcr = 0;
148
+ for (const frame of frames) {
149
+ rms += frame.rms;
150
+ zcr += frame.zcr;
151
+ for (let i = 0; i < bandCount; i += 1) {
152
+ bands[i] = (bands[i] ?? 0) + (frame.bands[i] ?? 0);
153
+ }
154
+ }
155
+ const n = frames.length;
156
+ return {
157
+ bands: bands.map((b) => b / n),
158
+ rms: rms / n,
159
+ zcr: zcr / n,
160
+ };
161
+ }
162
+ function goertzelMagnitude(samples, targetHz, sampleRateHz) {
163
+ const omega = (2 * Math.PI * targetHz) / sampleRateHz;
164
+ const coeff = 2 * Math.cos(omega);
165
+ let s0 = 0;
166
+ let s1 = 0;
167
+ let s2 = 0;
168
+ for (let i = 0; i < samples.length; i += 1) {
169
+ const x = samples[i] / 32768;
170
+ s0 = x + coeff * s1 - s2;
171
+ s2 = s1;
172
+ s1 = s0;
173
+ }
174
+ const real = s1 - s2 * Math.cos(omega);
175
+ const imag = s2 * Math.sin(omega);
176
+ return Math.sqrt(real * real + imag * imag);
177
+ }
178
+ //# sourceMappingURL=primary-speaker-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"primary-speaker-gate.js","sourceRoot":"","sources":["../src/primary-speaker-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { type PipelineBus } from "./pipeline-bus.js";
2
+ import { type Scheduler } from "./scheduler.js";
3
+ export interface FallbackProvider<TReq, TResp> {
4
+ readonly id: string;
5
+ send(req: TReq, signal: AbortSignal): Promise<TResp>;
6
+ healthProbe(signal: AbortSignal): Promise<boolean>;
7
+ }
8
+ export interface ProviderFallbackOptions {
9
+ readonly bus: PipelineBus;
10
+ readonly contextId: string;
11
+ readonly attemptTimeoutMs: number;
12
+ readonly recoveryProbeIntervalMs: number;
13
+ readonly scheduler?: Scheduler;
14
+ }
15
+ export declare class ProviderFallback<TReq, TResp> {
16
+ private readonly providers;
17
+ private readonly opts;
18
+ private readonly unavailable;
19
+ private readonly recoveryTimers;
20
+ private readonly scheduler;
21
+ constructor(providers: readonly FallbackProvider<TReq, TResp>[], opts: ProviderFallbackOptions);
22
+ send(req: TReq): Promise<TResp>;
23
+ close(): void;
24
+ private markUnavailable;
25
+ private scheduleRecoveryProbe;
26
+ private metric;
27
+ }
28
+ //# sourceMappingURL=provider-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-fallback.d.ts","sourceRoot":"","sources":["../src/provider-fallback.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhE,MAAM,WAAW,gBAAgB,CAAC,IAAI,EAAE,KAAK;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACrD,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;CAChC;AAED,qBAAa,gBAAgB,CAAC,IAAI,EAAE,KAAK;IAMrC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IANvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAGnB,SAAS,EAAE,SAAS,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EACnD,IAAI,EAAE,uBAAuB;IAK1C,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;IAcrC,KAAK,IAAI,IAAI;IAMb,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,qBAAqB;IAoB7B,OAAO,CAAC,MAAM;CAGf"}