@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.
- package/dist/audio/alaw.d.ts +3 -0
- package/dist/audio/alaw.d.ts.map +1 -0
- package/dist/audio/alaw.js +53 -0
- package/dist/audio/alaw.js.map +1 -0
- package/dist/audio/g722.d.ts +39 -0
- package/dist/audio/g722.d.ts.map +1 -0
- package/dist/audio/g722.js +286 -0
- package/dist/audio/g722.js.map +1 -0
- package/dist/audio/index.d.ts +7 -0
- package/dist/audio/index.d.ts.map +1 -0
- package/dist/audio/index.js +8 -0
- package/dist/audio/index.js.map +1 -0
- package/dist/audio/loudness.d.ts +13 -0
- package/dist/audio/loudness.d.ts.map +1 -0
- package/dist/audio/loudness.js +65 -0
- package/dist/audio/loudness.js.map +1 -0
- package/dist/audio/mulaw.d.ts +3 -0
- package/dist/audio/mulaw.d.ts.map +1 -0
- package/dist/audio/mulaw.js +39 -0
- package/dist/audio/mulaw.js.map +1 -0
- package/dist/audio/pcm.d.ts +5 -0
- package/dist/audio/pcm.d.ts.map +1 -0
- package/dist/audio/pcm.js +40 -0
- package/dist/audio/pcm.js.map +1 -0
- package/dist/audio/resample.d.ts +12 -0
- package/dist/audio/resample.d.ts.map +1 -0
- package/dist/audio/resample.js +157 -0
- package/dist/audio/resample.js.map +1 -0
- package/dist/audio-envelope.d.ts +23 -0
- package/dist/audio-envelope.d.ts.map +1 -0
- package/dist/audio-envelope.js +114 -0
- package/dist/audio-envelope.js.map +1 -0
- package/dist/confidence-to-wait.d.ts +12 -0
- package/dist/confidence-to-wait.d.ts.map +1 -0
- package/dist/confidence-to-wait.js +18 -0
- package/dist/confidence-to-wait.js.map +1 -0
- package/dist/conversation-event.d.ts +19 -0
- package/dist/conversation-event.d.ts.map +1 -0
- package/dist/conversation-event.js +44 -0
- package/dist/conversation-event.js.map +1 -0
- package/dist/error-handler.d.ts +35 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +136 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/idle-timeout.d.ts +69 -0
- package/dist/idle-timeout.d.ts.map +1 -0
- package/dist/idle-timeout.js +149 -0
- package/dist/idle-timeout.js.map +1 -0
- package/dist/incremental-unit.d.ts +17 -0
- package/dist/incremental-unit.d.ts.map +1 -0
- package/dist/incremental-unit.js +5 -0
- package/dist/incremental-unit.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/init-chain.d.ts +35 -0
- package/dist/init-chain.d.ts.map +1 -0
- package/dist/init-chain.js +102 -0
- package/dist/init-chain.js.map +1 -0
- package/dist/init-stage-order.d.ts +5 -0
- package/dist/init-stage-order.d.ts.map +1 -0
- package/dist/init-stage-order.js +74 -0
- package/dist/init-stage-order.js.map +1 -0
- package/dist/interaction-coordinator.d.ts +45 -0
- package/dist/interaction-coordinator.d.ts.map +1 -0
- package/dist/interaction-coordinator.js +197 -0
- package/dist/interaction-coordinator.js.map +1 -0
- package/dist/interaction-policy.d.ts +103 -0
- package/dist/interaction-policy.d.ts.map +1 -0
- package/dist/interaction-policy.js +6 -0
- package/dist/interaction-policy.js.map +1 -0
- package/dist/iu-ledger.d.ts +39 -0
- package/dist/iu-ledger.d.ts.map +1 -0
- package/dist/iu-ledger.js +90 -0
- package/dist/iu-ledger.js.map +1 -0
- package/dist/latency-filler-fixtures.d.ts +7 -0
- package/dist/latency-filler-fixtures.d.ts.map +1 -0
- package/dist/latency-filler-fixtures.js +10 -0
- package/dist/latency-filler-fixtures.js.map +1 -0
- package/dist/latency-filler.d.ts +29 -0
- package/dist/latency-filler.d.ts.map +1 -0
- package/dist/latency-filler.js +102 -0
- package/dist/latency-filler.js.map +1 -0
- package/dist/mode-switcher.d.ts +24 -0
- package/dist/mode-switcher.d.ts.map +1 -0
- package/dist/mode-switcher.js +83 -0
- package/dist/mode-switcher.js.map +1 -0
- package/dist/observability-observer.d.ts +36 -0
- package/dist/observability-observer.d.ts.map +1 -0
- package/dist/observability-observer.js +141 -0
- package/dist/observability-observer.js.map +1 -0
- package/dist/observability.d.ts +68 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +70 -0
- package/dist/observability.js.map +1 -0
- package/dist/packet-factories.d.ts +58 -0
- package/dist/packet-factories.d.ts.map +1 -0
- package/dist/packet-factories.js +181 -0
- package/dist/packet-factories.js.map +1 -0
- package/dist/packets.d.ts +532 -0
- package/dist/packets.d.ts.map +1 -0
- package/dist/packets.js +51 -0
- package/dist/packets.js.map +1 -0
- package/dist/pipeline-bus.d.ts +92 -0
- package/dist/pipeline-bus.d.ts.map +1 -0
- package/dist/pipeline-bus.js +294 -0
- package/dist/pipeline-bus.js.map +1 -0
- package/dist/plugin-contract.d.ts +73 -0
- package/dist/plugin-contract.d.ts.map +1 -0
- package/dist/plugin-contract.js +30 -0
- package/dist/plugin-contract.js.map +1 -0
- package/dist/policies/defer.d.ts +11 -0
- package/dist/policies/defer.d.ts.map +1 -0
- package/dist/policies/defer.js +13 -0
- package/dist/policies/defer.js.map +1 -0
- package/dist/policies/rule-based.d.ts +35 -0
- package/dist/policies/rule-based.d.ts.map +1 -0
- package/dist/policies/rule-based.js +130 -0
- package/dist/policies/rule-based.js.map +1 -0
- package/dist/pricing.d.ts +34 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +108 -0
- package/dist/pricing.js.map +1 -0
- package/dist/primary-speaker-fixtures.d.ts +12 -0
- package/dist/primary-speaker-fixtures.d.ts.map +1 -0
- package/dist/primary-speaker-fixtures.js +37 -0
- package/dist/primary-speaker-fixtures.js.map +1 -0
- package/dist/primary-speaker-gate.d.ts +42 -0
- package/dist/primary-speaker-gate.d.ts.map +1 -0
- package/dist/primary-speaker-gate.js +178 -0
- package/dist/primary-speaker-gate.js.map +1 -0
- package/dist/provider-fallback.d.ts +28 -0
- package/dist/provider-fallback.d.ts.map +1 -0
- package/dist/provider-fallback.js +72 -0
- package/dist/provider-fallback.js.map +1 -0
- package/dist/reasoner-hedge.d.ts +21 -0
- package/dist/reasoner-hedge.d.ts.map +1 -0
- package/dist/reasoner-hedge.js +170 -0
- package/dist/reasoner-hedge.js.map +1 -0
- package/dist/reasoner-route.d.ts +24 -0
- package/dist/reasoner-route.d.ts.map +1 -0
- package/dist/reasoner-route.js +87 -0
- package/dist/reasoner-route.js.map +1 -0
- package/dist/reasoner-session-store.d.ts +18 -0
- package/dist/reasoner-session-store.d.ts.map +1 -0
- package/dist/reasoner-session-store.js +21 -0
- package/dist/reasoner-session-store.js.map +1 -0
- package/dist/reasoner.d.ts +97 -0
- package/dist/reasoner.d.ts.map +1 -0
- package/dist/reasoner.js +9 -0
- package/dist/reasoner.js.map +1 -0
- package/dist/retry.d.ts +31 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +83 -0
- package/dist/retry.js.map +1 -0
- package/dist/scheduler.d.ts +11 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +20 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/spend-cap.d.ts +27 -0
- package/dist/spend-cap.d.ts.map +1 -0
- package/dist/spend-cap.js +38 -0
- package/dist/spend-cap.js.map +1 -0
- package/dist/tts-playout-clock.d.ts +43 -0
- package/dist/tts-playout-clock.d.ts.map +1 -0
- package/dist/tts-playout-clock.js +125 -0
- package/dist/tts-playout-clock.js.map +1 -0
- package/dist/turn-arbiter.d.ts +41 -0
- package/dist/turn-arbiter.d.ts.map +1 -0
- package/dist/turn-arbiter.js +259 -0
- package/dist/turn-arbiter.js.map +1 -0
- package/dist/voice-agent-session-util.d.ts +60 -0
- package/dist/voice-agent-session-util.d.ts.map +1 -0
- package/dist/voice-agent-session-util.js +204 -0
- package/dist/voice-agent-session-util.js.map +1 -0
- package/dist/voice-agent-session.d.ts +435 -0
- package/dist/voice-agent-session.d.ts.map +1 -0
- package/dist/voice-agent-session.js +1597 -0
- package/dist/voice-agent-session.js.map +1 -0
- package/dist/voice-text.d.ts +46 -0
- package/dist/voice-text.d.ts.map +1 -0
- package/dist/voice-text.js +209 -0
- package/dist/voice-text.js.map +1 -0
- package/package.json +19 -6
- package/src/audio/alaw.test.ts +87 -0
- package/src/audio/audio.test.ts +285 -0
- package/src/audio/g722.test.ts +149 -0
- package/src/audio/loudness.test.ts +58 -0
- package/src/audio-envelope.test.ts +167 -0
- package/src/confidence-to-wait.test.ts +21 -0
- package/src/error-handler.test.ts +56 -0
- package/src/hedge-throwing-backend.test.ts +46 -0
- package/src/interaction-coordinator.test.ts +529 -0
- package/src/iu-ledger.test.ts +276 -0
- package/src/latency-filler.test.ts +62 -0
- package/src/observability-observer.test.ts +248 -0
- package/src/observability.test.ts +94 -0
- package/src/packet-factories.test.ts +126 -0
- package/src/pipeline-bus.g10.test.ts +145 -0
- package/src/pipeline-bus.test.ts +211 -0
- package/src/policies/defer.test.ts +86 -0
- package/src/policies/rule-based.test.ts +334 -0
- package/src/pricing.test.ts +226 -0
- package/src/primary-speaker-gate.test.ts +150 -0
- package/src/provider-fallback.test.ts +87 -0
- package/src/reasoner-hedge.test.ts +361 -0
- package/src/reasoner-route.test.ts +248 -0
- package/src/reasoner.test.ts +69 -0
- package/src/retry.test.ts +83 -0
- package/src/route-throwing-spec.test.ts +44 -0
- package/src/spend-cap.test.ts +114 -0
- package/src/tts-playout-clock.test.ts +125 -0
- package/src/turn-arbiter.characterization.test.ts +477 -0
- package/src/turn-arbiter.test.ts +762 -0
- package/src/voice-agent-session.test.ts +4165 -0
- package/src/voice-text.test.ts +156 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
export function pcm16BytesToSamples(audio) {
|
|
3
|
+
if (audio.byteLength % 2 !== 0) {
|
|
4
|
+
throw new Error("PCM16 audio payload must contain an even number of bytes");
|
|
5
|
+
}
|
|
6
|
+
const view = new DataView(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
7
|
+
const samples = new Int16Array(audio.byteLength / 2);
|
|
8
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
9
|
+
samples[i] = view.getInt16(i * 2, true);
|
|
10
|
+
}
|
|
11
|
+
return samples;
|
|
12
|
+
}
|
|
13
|
+
export function pcm16SamplesToBytes(samples) {
|
|
14
|
+
const bytes = new Uint8Array(samples.byteLength);
|
|
15
|
+
const view = new DataView(bytes.buffer);
|
|
16
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
17
|
+
view.setInt16(i * 2, samples[i], true);
|
|
18
|
+
}
|
|
19
|
+
return bytes;
|
|
20
|
+
}
|
|
21
|
+
export function bigEndianPcm16BytesToSamples(audio) {
|
|
22
|
+
if (audio.byteLength % 2 !== 0) {
|
|
23
|
+
throw new Error("L16 audio payload must contain an even number of bytes");
|
|
24
|
+
}
|
|
25
|
+
const view = new DataView(audio.buffer, audio.byteOffset, audio.byteLength);
|
|
26
|
+
const samples = new Int16Array(audio.byteLength / 2);
|
|
27
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
28
|
+
samples[i] = view.getInt16(i * 2, false);
|
|
29
|
+
}
|
|
30
|
+
return samples;
|
|
31
|
+
}
|
|
32
|
+
export function pcm16SamplesToBigEndianBytes(samples) {
|
|
33
|
+
const bytes = new Uint8Array(samples.byteLength);
|
|
34
|
+
const view = new DataView(bytes.buffer);
|
|
35
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
36
|
+
view.setInt16(i * 2, samples[i], false);
|
|
37
|
+
}
|
|
38
|
+
return bytes;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=pcm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pcm.js","sourceRoot":"","sources":["../../src/audio/pcm.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACnD,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACrD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAmB;IACrD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,KAAiB;IAC5D,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;IACrD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,OAAmB;IAC9D,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function resamplePcm16(input: Int16Array, sourceSampleRateHz: number, targetSampleRateHz: number): Int16Array;
|
|
2
|
+
export declare class StreamingPcm16Resampler {
|
|
3
|
+
private history;
|
|
4
|
+
private readonly ratio;
|
|
5
|
+
private readonly fir;
|
|
6
|
+
private readonly sourceSampleRateHz;
|
|
7
|
+
private readonly targetSampleRateHz;
|
|
8
|
+
constructor(sourceSampleRateHz: number, targetSampleRateHz: number);
|
|
9
|
+
process(input: Int16Array): Int16Array;
|
|
10
|
+
}
|
|
11
|
+
export declare function resamplePcm16Streaming(resamplers: Map<string, StreamingPcm16Resampler>, input: Int16Array, sourceSampleRateHz: number, targetSampleRateHz: number): Int16Array;
|
|
12
|
+
//# sourceMappingURL=resample.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resample.d.ts","sourceRoot":"","sources":["../../src/audio/resample.ts"],"names":[],"mappings":"AA0FA,wBAAgB,aAAa,CAC3B,KAAK,EAAE,UAAU,EACjB,kBAAkB,EAAE,MAAM,EAC1B,kBAAkB,EAAE,MAAM,GACzB,UAAU,CAcZ;AAED,qBAAa,uBAAuB;IAClC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEhC,kBAAkB,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM;IAYlE,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU;CA8BvC;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,uBAAuB,CAAC,EAChD,KAAK,EAAE,UAAU,EACjB,kBAAkB,EAAE,MAAM,EAC1B,kBAAkB,EAAE,MAAM,GACzB,UAAU,CAUZ"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Windowed-sinc FIR low-pass filter for anti-alias downsampling.
|
|
3
|
+
// Hann window, 127 taps — gives ~44 dB min stopband attenuation, sufficient for
|
|
4
|
+
// the ≥40 dB alias-suppression requirement (F3). A polyphase structure would be
|
|
5
|
+
// faster but this direct-form is simpler to audit and correct for our frame sizes.
|
|
6
|
+
const FIR_TAPS = 127;
|
|
7
|
+
function buildLowPassFir(cutoffNormalized) {
|
|
8
|
+
// cutoffNormalized: fraction of the source sample rate (0 = DC, 0.5 = Nyquist).
|
|
9
|
+
const M = FIR_TAPS - 1; // filter order (126 for 127 taps)
|
|
10
|
+
const h = new Float64Array(FIR_TAPS);
|
|
11
|
+
let sum = 0;
|
|
12
|
+
for (let n = 0; n < FIR_TAPS; n += 1) {
|
|
13
|
+
const delay = n - M / 2; // centered delay: -63..0..63
|
|
14
|
+
const sinc = delay === 0
|
|
15
|
+
? 2 * cutoffNormalized
|
|
16
|
+
: Math.sin(2 * Math.PI * cutoffNormalized * delay) / (Math.PI * delay);
|
|
17
|
+
const hann = 0.5 - 0.5 * Math.cos((2 * Math.PI * n) / M);
|
|
18
|
+
h[n] = sinc * hann;
|
|
19
|
+
sum += h[n];
|
|
20
|
+
}
|
|
21
|
+
// Normalize for unity DC gain.
|
|
22
|
+
for (let n = 0; n < FIR_TAPS; n += 1) {
|
|
23
|
+
h[n] /= sum;
|
|
24
|
+
}
|
|
25
|
+
return h;
|
|
26
|
+
}
|
|
27
|
+
// The FIR depends only on the (source,target) rate pair via its cutoff, which is
|
|
28
|
+
// constant per connection. resamplePcm16 runs per audio chunk in the hot path, so
|
|
29
|
+
// cache the kernel instead of rebuilding 127 sinc·Hann taps on every call. Bounded:
|
|
30
|
+
// real deployments use a handful of distinct rate pairs (24k/16k→8k, 48k→16k, …).
|
|
31
|
+
const firCache = new Map();
|
|
32
|
+
function getLowPassFir(cutoffNormalized) {
|
|
33
|
+
const key = Math.round(cutoffNormalized * 1e6);
|
|
34
|
+
let fir = firCache.get(key);
|
|
35
|
+
if (fir === undefined) {
|
|
36
|
+
fir = buildLowPassFir(cutoffNormalized);
|
|
37
|
+
firCache.set(key, fir);
|
|
38
|
+
}
|
|
39
|
+
return fir;
|
|
40
|
+
}
|
|
41
|
+
// Evaluate the symmetric FIR centered at each decimated output position.
|
|
42
|
+
// Using the symmetric (zero-delay) formula avoids a group-delay shift at the
|
|
43
|
+
// cost of needing halfTaps future samples — which are available because we
|
|
44
|
+
// process a complete chunk at once. Edge samples (first / last halfTaps/ratio
|
|
45
|
+
// output samples) use only the available taps; their amplitude tapers due to
|
|
46
|
+
// the missing context, which is the accepted trade-off for stateless chunk
|
|
47
|
+
// processing.
|
|
48
|
+
function firDecimate(input, outputLength, ratio, fir) {
|
|
49
|
+
const M = fir.length;
|
|
50
|
+
const halfTaps = Math.floor(M / 2); // 63 for 127-tap filter
|
|
51
|
+
const output = new Int16Array(outputLength);
|
|
52
|
+
for (let m = 0; m < outputLength; m += 1) {
|
|
53
|
+
const n0 = Math.round(m * ratio); // nearest source position for output m
|
|
54
|
+
let acc = 0;
|
|
55
|
+
// Centered: h[0] multiplies x[n0+halfTaps], h[halfTaps] multiplies x[n0],
|
|
56
|
+
// h[M-1] multiplies x[n0-halfTaps].
|
|
57
|
+
for (let k = 0; k < M; k += 1) {
|
|
58
|
+
const srcIdx = n0 - k + halfTaps;
|
|
59
|
+
if (srcIdx >= 0 && srcIdx < input.length) {
|
|
60
|
+
acc += fir[k] * input[srcIdx];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Clamp to Int16 range; FIR sum can slightly exceed due to rounding.
|
|
64
|
+
output[m] = Math.max(-32768, Math.min(32767, Math.round(acc)));
|
|
65
|
+
}
|
|
66
|
+
return output;
|
|
67
|
+
}
|
|
68
|
+
function linearInterpolate(input, outputLength, ratio) {
|
|
69
|
+
const output = new Int16Array(outputLength);
|
|
70
|
+
for (let i = 0; i < outputLength; i += 1) {
|
|
71
|
+
const pos = i * ratio;
|
|
72
|
+
const lo = Math.floor(pos);
|
|
73
|
+
const hi = Math.min(input.length - 1, lo + 1);
|
|
74
|
+
const frac = pos - lo;
|
|
75
|
+
output[i] = Math.round(input[lo] * (1 - frac) + input[hi] * frac);
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
function outputLengthFor(inputLength, sourceSampleRateHz, targetSampleRateHz) {
|
|
80
|
+
return Math.max(1, Math.round((inputLength * targetSampleRateHz) / sourceSampleRateHz));
|
|
81
|
+
}
|
|
82
|
+
export function resamplePcm16(input, sourceSampleRateHz, targetSampleRateHz) {
|
|
83
|
+
if (input.length === 0)
|
|
84
|
+
return new Int16Array(0);
|
|
85
|
+
if (sourceSampleRateHz === targetSampleRateHz)
|
|
86
|
+
return input;
|
|
87
|
+
const outputLength = outputLengthFor(input.length, sourceSampleRateHz, targetSampleRateHz);
|
|
88
|
+
const ratio = sourceSampleRateHz / targetSampleRateHz;
|
|
89
|
+
if (ratio <= 1) {
|
|
90
|
+
return linearInterpolate(input, outputLength, ratio);
|
|
91
|
+
}
|
|
92
|
+
const cutoffNormalized = (0.45 * targetSampleRateHz) / sourceSampleRateHz;
|
|
93
|
+
const fir = getLowPassFir(cutoffNormalized);
|
|
94
|
+
return firDecimate(input, outputLength, ratio, fir);
|
|
95
|
+
}
|
|
96
|
+
export class StreamingPcm16Resampler {
|
|
97
|
+
history = new Int16Array(0);
|
|
98
|
+
ratio;
|
|
99
|
+
fir;
|
|
100
|
+
sourceSampleRateHz;
|
|
101
|
+
targetSampleRateHz;
|
|
102
|
+
constructor(sourceSampleRateHz, targetSampleRateHz) {
|
|
103
|
+
this.sourceSampleRateHz = sourceSampleRateHz;
|
|
104
|
+
this.targetSampleRateHz = targetSampleRateHz;
|
|
105
|
+
this.ratio = sourceSampleRateHz / targetSampleRateHz;
|
|
106
|
+
if (this.ratio > 1) {
|
|
107
|
+
const cutoffNormalized = (0.45 * targetSampleRateHz) / sourceSampleRateHz;
|
|
108
|
+
this.fir = getLowPassFir(cutoffNormalized);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.fir = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
process(input) {
|
|
115
|
+
if (input.length === 0)
|
|
116
|
+
return new Int16Array(0);
|
|
117
|
+
if (this.sourceSampleRateHz === this.targetSampleRateHz)
|
|
118
|
+
return input;
|
|
119
|
+
if (this.ratio <= 1 || this.fir === null) {
|
|
120
|
+
const outputLength = outputLengthFor(input.length, this.sourceSampleRateHz, this.targetSampleRateHz);
|
|
121
|
+
return linearInterpolate(input, outputLength, this.ratio);
|
|
122
|
+
}
|
|
123
|
+
const historyLength = this.history.length;
|
|
124
|
+
const combined = new Int16Array(historyLength + input.length);
|
|
125
|
+
combined.set(this.history, 0);
|
|
126
|
+
combined.set(input, historyLength);
|
|
127
|
+
const fullOutputLength = outputLengthFor(combined.length, this.sourceSampleRateHz, this.targetSampleRateHz);
|
|
128
|
+
const fullOutput = firDecimate(combined, fullOutputLength, this.ratio, this.fir);
|
|
129
|
+
let firstNewOutput = 0;
|
|
130
|
+
if (historyLength > 0) {
|
|
131
|
+
while (firstNewOutput < fullOutput.length) {
|
|
132
|
+
const centerInputIndex = Math.round(firstNewOutput * this.ratio);
|
|
133
|
+
if (centerInputIndex >= historyLength)
|
|
134
|
+
break;
|
|
135
|
+
firstNewOutput += 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const output = fullOutput.subarray(firstNewOutput);
|
|
139
|
+
const keepHistory = Math.min(FIR_TAPS - 1, combined.length);
|
|
140
|
+
this.history = combined.subarray(combined.length - keepHistory);
|
|
141
|
+
return output;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export function resamplePcm16Streaming(resamplers, input, sourceSampleRateHz, targetSampleRateHz) {
|
|
145
|
+
if (input.length === 0)
|
|
146
|
+
return new Int16Array(0);
|
|
147
|
+
if (sourceSampleRateHz === targetSampleRateHz)
|
|
148
|
+
return input;
|
|
149
|
+
const key = `${String(sourceSampleRateHz)}->${String(targetSampleRateHz)}`;
|
|
150
|
+
let resampler = resamplers.get(key);
|
|
151
|
+
if (!resampler) {
|
|
152
|
+
resampler = new StreamingPcm16Resampler(sourceSampleRateHz, targetSampleRateHz);
|
|
153
|
+
resamplers.set(key, resampler);
|
|
154
|
+
}
|
|
155
|
+
return resampler.process(input);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=resample.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resample.js","sourceRoot":"","sources":["../../src/audio/resample.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AudioFormat } from "./packets.js";
|
|
2
|
+
export declare const SYRINX_AUDIO_ENVELOPE_NAME: "syrinx.audio.v1";
|
|
3
|
+
export declare const SYRINX_AUDIO_ENVELOPE_MAGIC: Uint8Array<ArrayBuffer>;
|
|
4
|
+
export interface SyrinxAudioEnvelopeHeader {
|
|
5
|
+
readonly type: "audio";
|
|
6
|
+
readonly contextId?: string;
|
|
7
|
+
readonly sampleRateHz: number;
|
|
8
|
+
readonly sequence?: number;
|
|
9
|
+
readonly encoding?: "pcm_s16le" | "opus";
|
|
10
|
+
readonly channels?: 1;
|
|
11
|
+
readonly byteLength?: number;
|
|
12
|
+
readonly durationMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SyrinxAudioEnvelope {
|
|
15
|
+
readonly header: SyrinxAudioEnvelopeHeader;
|
|
16
|
+
readonly audio: Uint8Array;
|
|
17
|
+
}
|
|
18
|
+
export declare function assertAudioFormat(format: AudioFormat): void;
|
|
19
|
+
export declare function assertAudioPayload(format: AudioFormat, audio: Uint8Array): void;
|
|
20
|
+
export declare function encodeSyrinxAudioEnvelope(header: SyrinxAudioEnvelopeHeader, audio: Uint8Array): Uint8Array;
|
|
21
|
+
export declare function decodeSyrinxAudioEnvelope(data: Uint8Array): SyrinxAudioEnvelope;
|
|
22
|
+
export declare function hasSyrinxAudioEnvelope(data: Uint8Array): boolean;
|
|
23
|
+
//# sourceMappingURL=audio-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-envelope.d.ts","sourceRoot":"","sources":["../src/audio-envelope.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,eAAO,MAAM,0BAA0B,EAAG,iBAA0B,CAAC;AACrE,eAAO,MAAM,2BAA2B,yBAA+C,CAAC;AAExF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAK3D;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAgB/E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,yBAAyB,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAU1G;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,UAAU,GAAG,mBAAmB,CA2B/E;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAMhE"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
export const SYRINX_AUDIO_ENVELOPE_NAME = "syrinx.audio.v1";
|
|
3
|
+
export const SYRINX_AUDIO_ENVELOPE_MAGIC = new Uint8Array([83, 89, 82, 88, 65, 49, 10]);
|
|
4
|
+
export function assertAudioFormat(format) {
|
|
5
|
+
if (format.channels !== 1)
|
|
6
|
+
throw new Error("audio must be mono");
|
|
7
|
+
if (!Number.isInteger(format.sampleRateHz) || format.sampleRateHz <= 0) {
|
|
8
|
+
throw new Error("sampleRateHz must be a positive integer");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function assertAudioPayload(format, audio) {
|
|
12
|
+
if (format.encoding === "opus") {
|
|
13
|
+
if (audio.byteLength === 0) {
|
|
14
|
+
throw new Error("opus payload must not be empty");
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (format.encoding === "mulaw") {
|
|
19
|
+
if (audio.byteLength === 0) {
|
|
20
|
+
throw new Error("mulaw payload must not be empty");
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (audio.byteLength % 2 !== 0) {
|
|
25
|
+
throw new Error("PCM16 payload must contain an even number of bytes");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function encodeSyrinxAudioEnvelope(header, audio) {
|
|
29
|
+
validateSyrinxAudioEnvelope(header, audio);
|
|
30
|
+
const headerBytes = new TextEncoder().encode(JSON.stringify(header));
|
|
31
|
+
const output = new Uint8Array(SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength + 4 + headerBytes.byteLength + audio.byteLength);
|
|
32
|
+
output.set(SYRINX_AUDIO_ENVELOPE_MAGIC, 0);
|
|
33
|
+
new DataView(output.buffer, output.byteOffset, output.byteLength)
|
|
34
|
+
.setUint32(SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength, headerBytes.byteLength, true);
|
|
35
|
+
output.set(headerBytes, SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength + 4);
|
|
36
|
+
output.set(audio, SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength + 4 + headerBytes.byteLength);
|
|
37
|
+
return output;
|
|
38
|
+
}
|
|
39
|
+
export function decodeSyrinxAudioEnvelope(data) {
|
|
40
|
+
if (!hasSyrinxAudioEnvelope(data)) {
|
|
41
|
+
throw new Error("Syrinx binary audio envelope magic is missing");
|
|
42
|
+
}
|
|
43
|
+
const headerLengthOffset = SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength;
|
|
44
|
+
if (data.byteLength < headerLengthOffset + 4) {
|
|
45
|
+
throw new Error("Syrinx binary audio envelope is truncated");
|
|
46
|
+
}
|
|
47
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
48
|
+
const headerLength = view.getUint32(headerLengthOffset, true);
|
|
49
|
+
const headerStart = headerLengthOffset + 4;
|
|
50
|
+
const headerEnd = headerStart + headerLength;
|
|
51
|
+
if (headerLength <= 0 || headerEnd > data.byteLength) {
|
|
52
|
+
throw new Error("Syrinx binary audio envelope has an invalid header length");
|
|
53
|
+
}
|
|
54
|
+
const parsed = JSON.parse(new TextDecoder().decode(data.subarray(headerStart, headerEnd)));
|
|
55
|
+
if (!parsed || typeof parsed !== "object") {
|
|
56
|
+
throw new Error("Syrinx binary audio envelope header must be an object");
|
|
57
|
+
}
|
|
58
|
+
const header = parsed;
|
|
59
|
+
const audio = data.subarray(headerEnd);
|
|
60
|
+
validateSyrinxAudioEnvelope(header, audio);
|
|
61
|
+
return { header, audio };
|
|
62
|
+
}
|
|
63
|
+
export function hasSyrinxAudioEnvelope(data) {
|
|
64
|
+
if (data.byteLength < SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength)
|
|
65
|
+
return false;
|
|
66
|
+
for (let i = 0; i < SYRINX_AUDIO_ENVELOPE_MAGIC.byteLength; i += 1) {
|
|
67
|
+
if (data[i] !== SYRINX_AUDIO_ENVELOPE_MAGIC[i])
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
function validateSyrinxAudioEnvelope(header, audio) {
|
|
73
|
+
if (header.type !== "audio") {
|
|
74
|
+
throw new Error("Syrinx binary audio envelope type must be audio");
|
|
75
|
+
}
|
|
76
|
+
if (!isPositiveInteger(header.sampleRateHz)) {
|
|
77
|
+
throw new Error("Syrinx binary audio envelope sampleRateHz must be a positive integer");
|
|
78
|
+
}
|
|
79
|
+
if (header.encoding && header.encoding !== "pcm_s16le" && header.encoding !== "opus") {
|
|
80
|
+
throw new Error(`Unsupported Syrinx binary audio encoding: ${header.encoding}`);
|
|
81
|
+
}
|
|
82
|
+
if (header.sequence !== undefined && !isNonNegativeInteger(header.sequence)) {
|
|
83
|
+
throw new Error("Syrinx binary audio envelope sequence must be a non-negative integer");
|
|
84
|
+
}
|
|
85
|
+
if (header.durationMs !== undefined && !isNonNegativeInteger(header.durationMs)) {
|
|
86
|
+
throw new Error("Syrinx binary audio envelope durationMs must be a non-negative integer");
|
|
87
|
+
}
|
|
88
|
+
if (header.byteLength !== undefined && !isNonNegativeInteger(header.byteLength)) {
|
|
89
|
+
throw new Error("Syrinx binary audio envelope byteLength must be a non-negative integer");
|
|
90
|
+
}
|
|
91
|
+
if (header.byteLength !== undefined && header.byteLength !== audio.byteLength) {
|
|
92
|
+
throw new Error("Syrinx binary audio envelope byteLength does not match payload");
|
|
93
|
+
}
|
|
94
|
+
const format = {
|
|
95
|
+
encoding: header.encoding === "opus" ? "opus" : "pcm_s16le",
|
|
96
|
+
sampleRateHz: header.sampleRateHz,
|
|
97
|
+
channels: header.channels ?? 1,
|
|
98
|
+
};
|
|
99
|
+
assertAudioFormat(format);
|
|
100
|
+
assertAudioPayload(format, audio);
|
|
101
|
+
if (format.encoding !== "opus" && header.durationMs !== undefined) {
|
|
102
|
+
const expectedDurationMs = Math.round((audio.byteLength / 2 / header.sampleRateHz) * 1000);
|
|
103
|
+
if (Math.abs(header.durationMs - expectedDurationMs) > 1) {
|
|
104
|
+
throw new Error("Syrinx binary audio envelope durationMs does not match payload and sampleRateHz");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function isPositiveInteger(value) {
|
|
109
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
110
|
+
}
|
|
111
|
+
function isNonNegativeInteger(value) {
|
|
112
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=audio-envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-envelope.js","sourceRoot":"","sources":["../src/audio-envelope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ConfidenceToWaitConfig {
|
|
2
|
+
/** Wait at confidence = 1.0 (high EOT certainty). Default: 150 ms. */
|
|
3
|
+
readonly minWaitMs?: number;
|
|
4
|
+
/** Wait at confidence = 0.0 (low EOT certainty). Default: 2000 ms. */
|
|
5
|
+
readonly maxWaitMs?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Maps an end-of-turn confidence score to a bounded finalize wait (LiveKit-style).
|
|
9
|
+
* Higher confidence → shorter wait. Monotonic, clamped, deterministic.
|
|
10
|
+
*/
|
|
11
|
+
export declare function confidenceToWaitMs(confidence: number, config?: ConfidenceToWaitConfig): number;
|
|
12
|
+
//# sourceMappingURL=confidence-to-wait.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence-to-wait.d.ts","sourceRoot":"","sources":["../src/confidence-to-wait.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,sBAA2B,GAClC,MAAM,CAUR"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
const DEFAULT_MIN_WAIT_MS = 150;
|
|
3
|
+
const DEFAULT_MAX_WAIT_MS = 2000;
|
|
4
|
+
/**
|
|
5
|
+
* Maps an end-of-turn confidence score to a bounded finalize wait (LiveKit-style).
|
|
6
|
+
* Higher confidence → shorter wait. Monotonic, clamped, deterministic.
|
|
7
|
+
*/
|
|
8
|
+
export function confidenceToWaitMs(confidence, config = {}) {
|
|
9
|
+
const minWaitMs = config.minWaitMs ?? DEFAULT_MIN_WAIT_MS;
|
|
10
|
+
const maxWaitMs = config.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
|
|
11
|
+
if (maxWaitMs < minWaitMs) {
|
|
12
|
+
throw new Error(`maxWaitMs (${String(maxWaitMs)}) must be >= minWaitMs (${String(minWaitMs)})`);
|
|
13
|
+
}
|
|
14
|
+
const clamped = Math.min(1, Math.max(0, confidence));
|
|
15
|
+
const span = maxWaitMs - minWaitMs;
|
|
16
|
+
return Math.round(maxWaitMs - clamped * span);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=confidence-to-wait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence-to-wait.js","sourceRoot":"","sources":["../src/confidence-to-wait.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAS/B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAAkB,EAClB,SAAiC,EAAE;IAEnC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,mBAAmB,CAAC;IAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,mBAAmB,CAAC;IAC1D,IAAI,SAAS,GAAG,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,cAAc,MAAM,CAAC,SAAS,CAAC,2BAA2B,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,SAAS,GAAG,SAAS,CAAC;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ConversationEvent {
|
|
2
|
+
/** Component that emitted the event: "vad", "stt", "eos", "llm", "tts", "tool", "session", "turn" */
|
|
3
|
+
readonly component: string;
|
|
4
|
+
/** Sub-type: "speech_started", "interim", "final", "delta", "first_audio", "call_started", etc. */
|
|
5
|
+
readonly type: string;
|
|
6
|
+
/** Arbitrary key-value payload. Always includes "context_id". */
|
|
7
|
+
readonly data: Readonly<Record<string, string>>;
|
|
8
|
+
/** Wall-clock time of the event in ms since epoch. */
|
|
9
|
+
readonly timestampMs: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a ReadableStream of ConversationEvents that is fed by a callback.
|
|
13
|
+
* Returns [stream, push] — call push(event) to enqueue, consumers read from stream.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createConversationEventStream(): [
|
|
16
|
+
ReadableStream<ConversationEvent>,
|
|
17
|
+
(event: ConversationEvent) => void
|
|
18
|
+
];
|
|
19
|
+
//# sourceMappingURL=conversation-event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-event.d.ts","sourceRoot":"","sources":["../src/conversation-event.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,iBAAiB;IAChC,qGAAqG;IACrG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mGAAmG;IACnG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAMD;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI;IAC/C,cAAc,CAAC,iBAAiB,CAAC;IACjC,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI;CACnC,CA4BA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Syrinx Kernel v2 — Conversation Event (Debug Stream)
|
|
4
|
+
//
|
|
5
|
+
// Normalized event type consumed by debugger UIs and the VoiceSessionRecorder.
|
|
6
|
+
// Every pipeline component emits these alongside their functional packets.
|
|
7
|
+
// Exposed as `session.debugEvents: ReadableStream<ConversationEvent>`.
|
|
8
|
+
//
|
|
9
|
+
// Design decision (per RFC Q3 resolution): events flow through the bus on
|
|
10
|
+
// the Background route. If Background saturates, drops are metric'd.
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Factory
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Create a ReadableStream of ConversationEvents that is fed by a callback.
|
|
16
|
+
* Returns [stream, push] — call push(event) to enqueue, consumers read from stream.
|
|
17
|
+
*/
|
|
18
|
+
export function createConversationEventStream() {
|
|
19
|
+
let controller = null;
|
|
20
|
+
const stream = new ReadableStream({
|
|
21
|
+
start(c) {
|
|
22
|
+
controller = c;
|
|
23
|
+
},
|
|
24
|
+
cancel() {
|
|
25
|
+
controller = null;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const push = (event) => {
|
|
29
|
+
// Only retain events when a reader is attached. A ReadableStream buffers
|
|
30
|
+
// enqueue() without bound; with no debug consumer (nothing in-tree reads this
|
|
31
|
+
// by default) every packet's event would be retained for the whole call and
|
|
32
|
+
// grow memory unbounded. `locked` is true once getReader() is called.
|
|
33
|
+
if (controller && stream.locked) {
|
|
34
|
+
try {
|
|
35
|
+
controller.enqueue(event);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Stream closed — drop
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
return [stream, push];
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=conversation-event.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-event.js","sourceRoot":"","sources":["../src/conversation-event.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,uDAAuD;AACvD,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,uEAAuE;AACvE,EAAE;AACF,0EAA0E;AAC1E,qEAAqE;AAiBrE,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,UAAU,6BAA6B;IAI3C,IAAI,UAAU,GACZ,IAAI,CAAC;IAEP,MAAM,MAAM,GAAG,IAAI,cAAc,CAAoB;QACnD,KAAK,CAAC,CAAC;YACL,UAAU,GAAG,CAAC,CAAC;QACjB,CAAC;QACD,MAAM;YACJ,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;KACF,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,CAAC,KAAwB,EAAQ,EAAE;QAC9C,yEAAyE;QACzE,8EAA8E;QAC9E,4EAA4E;QAC5E,sEAAsE;QACtE,IAAI,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,uBAAuB;YACzB,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ErrorCategory, type VoiceErrorPacket } from "./packets.js";
|
|
2
|
+
/**
|
|
3
|
+
* Categorize an error from the STT provider.
|
|
4
|
+
*
|
|
5
|
+
* Heuristic:
|
|
6
|
+
* - HTTP 429 → RateLimit (recoverable)
|
|
7
|
+
* - Network errors (ECONNRESET, ETIMEDOUT, ENOTFOUND) → NetworkTimeout (recoverable)
|
|
8
|
+
* - HTTP 401/403 → Authentication (fatal)
|
|
9
|
+
* - HTTP 400/422 → InvalidInput (fatal)
|
|
10
|
+
* - HTTP 402 → ResourceExhausted (fatal)
|
|
11
|
+
* - Everything else → InternalFault (fatal)
|
|
12
|
+
*/
|
|
13
|
+
export declare function categorizeSttError(err: unknown): ErrorCategory;
|
|
14
|
+
/**
|
|
15
|
+
* Categorize an error from the TTS provider.
|
|
16
|
+
* Same heuristic as STT — TTS providers have similar error patterns.
|
|
17
|
+
*/
|
|
18
|
+
export declare function categorizeTtsError(err: unknown): ErrorCategory;
|
|
19
|
+
/**
|
|
20
|
+
* Categorize an error from the LLM provider.
|
|
21
|
+
*
|
|
22
|
+
* Additional LLM-specific heuristics:
|
|
23
|
+
* - "context length exceeded" → InvalidInput (fatal)
|
|
24
|
+
* - "content filter" / "safety" → InvalidInput (fatal)
|
|
25
|
+
*/
|
|
26
|
+
export declare function categorizeLlmError(err: unknown): ErrorCategory;
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if the error category is recoverable (retry with backoff).
|
|
29
|
+
*/
|
|
30
|
+
export declare function isRecoverable(category: ErrorCategory): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Returns true if the error packet represents a fatal error.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isFatalError(pkt: VoiceErrorPacket): boolean;
|
|
35
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAMpE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAiD9D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAE9D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAc9D;AAMD;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAE3D"}
|