@kuralle-syrinx/vap 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.
@@ -0,0 +1,29 @@
1
+ import { type PluginConfig } from "@kuralle-syrinx/core";
2
+ import { type VapPredictor, type VapPredictorFrame, type VapProbs } from "./vap-policy.js";
3
+ export declare class LocalVapPredictor implements VapPredictor {
4
+ private ort;
5
+ private mimiSession;
6
+ private projectionSession;
7
+ private backboneSession;
8
+ private headsSession;
9
+ private manifest;
10
+ private mimiMetadata;
11
+ private readonly contexts;
12
+ initialize(cfg?: PluginConfig): Promise<void>;
13
+ push(frame: VapPredictorFrame): Promise<VapProbs>;
14
+ reset(contextId: string): void;
15
+ close(): Promise<void>;
16
+ private contextFor;
17
+ private newMimiState;
18
+ private resample;
19
+ private hasReadyWindow;
20
+ private inferWindow;
21
+ private encodeMimi;
22
+ private runBackbone;
23
+ private assertInitialized;
24
+ private requireOrt;
25
+ private requireManifest;
26
+ private requireMimiMetadata;
27
+ private requireSession;
28
+ }
29
+ //# sourceMappingURL=dualturn-predictor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dualturn-predictor.d.ts","sourceRoot":"","sources":["../src/dualturn-predictor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAiD,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACxG,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACd,MAAM,iBAAiB,CAAC;AAiDzB,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,iBAAiB,CAAiC;IAC1D,OAAO,CAAC,eAAe,CAAiC;IACxD,OAAO,CAAC,YAAY,CAAiC;IACrD,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsC;IAEzD,UAAU,CAAC,GAAG,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BjD,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkBvD,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,OAAO,CAAC,UAAU;IAsBlB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,cAAc;YAMR,WAAW;YAqBX,UAAU;YAoCV,WAAW;IAmCzB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,cAAc;CAIvB"}
@@ -0,0 +1,357 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import { readFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { StreamingPcm16Resampler, optionalStringConfig } from "@kuralle-syrinx/core";
6
+ import { ZERO_VAP_PROBS, } from "./vap-policy.js";
7
+ const DEFAULT_BUNDLE_PATH = fileURLToPath(new URL("../models/dualturn", import.meta.url));
8
+ const MODEL_SAMPLE_RATE_HZ = 24_000;
9
+ const MODEL_WINDOW_SAMPLES = 1_920;
10
+ const QUEUE_CAPACITY_SAMPLES = MODEL_SAMPLE_RATE_HZ * 2;
11
+ export class LocalVapPredictor {
12
+ ort = null;
13
+ mimiSession = null;
14
+ projectionSession = null;
15
+ backboneSession = null;
16
+ headsSession = null;
17
+ manifest = null;
18
+ mimiMetadata = null;
19
+ contexts = new Map();
20
+ async initialize(cfg = {}) {
21
+ const bundlePath = optionalStringConfig(cfg, "bundle_path")
22
+ ?? optionalStringConfig(cfg, "model_path")
23
+ ?? DEFAULT_BUNDLE_PATH;
24
+ const manifest = parseManifest(await readJson(join(bundlePath, "manifest.json")));
25
+ const mimiMetadata = parseMimiMetadata(await readJson(join(bundlePath, manifest.files.mimi_metadata)));
26
+ const ort = await import("onnxruntime-node");
27
+ const options = {
28
+ executionProviders: ["cpu"],
29
+ interOpNumThreads: 1,
30
+ intraOpNumThreads: 1,
31
+ };
32
+ const [mimiSession, projectionSession, backboneSession, headsSession] = await Promise.all([
33
+ ort.InferenceSession.create(join(bundlePath, manifest.files.mimi_model), options),
34
+ ort.InferenceSession.create(join(bundlePath, manifest.files.projection), options),
35
+ ort.InferenceSession.create(join(bundlePath, manifest.files.backbone), options),
36
+ ort.InferenceSession.create(join(bundlePath, manifest.files.heads), options),
37
+ ]);
38
+ this.ort = ort;
39
+ this.manifest = manifest;
40
+ this.mimiMetadata = mimiMetadata;
41
+ this.mimiSession = mimiSession;
42
+ this.projectionSession = projectionSession;
43
+ this.backboneSession = backboneSession;
44
+ this.headsSession = headsSession;
45
+ }
46
+ async push(frame) {
47
+ this.assertInitialized();
48
+ const context = this.contextFor(frame.contextId);
49
+ const resampled = this.resample(context, frame);
50
+ context.queues[frame.channel].append(resampled);
51
+ while (this.hasReadyWindow(context)) {
52
+ const user = context.queues.user.length >= MODEL_WINDOW_SAMPLES
53
+ ? context.queues.user.drainNormalized(MODEL_WINDOW_SAMPLES)
54
+ : new Float32Array(MODEL_WINDOW_SAMPLES);
55
+ const assistant = context.queues.assistant.length >= MODEL_WINDOW_SAMPLES
56
+ ? context.queues.assistant.drainNormalized(MODEL_WINDOW_SAMPLES)
57
+ : new Float32Array(MODEL_WINDOW_SAMPLES);
58
+ context.probs = await this.inferWindow(context, user, assistant);
59
+ }
60
+ return context.probs;
61
+ }
62
+ reset(contextId) {
63
+ this.contexts.delete(contextId);
64
+ }
65
+ async close() {
66
+ this.contexts.clear();
67
+ this.mimiSession = null;
68
+ this.projectionSession = null;
69
+ this.backboneSession = null;
70
+ this.headsSession = null;
71
+ this.mimiMetadata = null;
72
+ this.manifest = null;
73
+ this.ort = null;
74
+ }
75
+ contextFor(contextId) {
76
+ let context = this.contexts.get(contextId);
77
+ if (!context) {
78
+ context = {
79
+ queues: {
80
+ user: new PcmSampleQueue(QUEUE_CAPACITY_SAMPLES),
81
+ assistant: new PcmSampleQueue(QUEUE_CAPACITY_SAMPLES),
82
+ },
83
+ resamplers: new Map(),
84
+ mimi: {
85
+ user: this.newMimiState(),
86
+ assistant: this.newMimiState(),
87
+ },
88
+ backbonePast: [],
89
+ backbonePosition: 0,
90
+ probs: ZERO_VAP_PROBS,
91
+ };
92
+ this.contexts.set(contextId, context);
93
+ }
94
+ return context;
95
+ }
96
+ newMimiState() {
97
+ const ort = this.requireOrt();
98
+ const metadata = this.requireMimiMetadata();
99
+ const shapes = [
100
+ ...metadata.contract.spec.padding_cache_shapes,
101
+ ...metadata.contract.spec.past_key_value_shapes,
102
+ ];
103
+ return {
104
+ cachePosition: 0,
105
+ position: 0,
106
+ states: shapes.map((shape) => new ort.Tensor("float32", new Float32Array(product(shape)), [...shape])),
107
+ };
108
+ }
109
+ resample(context, frame) {
110
+ if (frame.sampleRateHz === MODEL_SAMPLE_RATE_HZ)
111
+ return frame.audio;
112
+ const key = `${frame.channel}:${String(frame.sampleRateHz)}`;
113
+ let resampler = context.resamplers.get(key);
114
+ if (!resampler) {
115
+ resampler = new StreamingPcm16Resampler(frame.sampleRateHz, MODEL_SAMPLE_RATE_HZ);
116
+ context.resamplers.set(key, resampler);
117
+ }
118
+ return resampler.process(frame.audio);
119
+ }
120
+ hasReadyWindow(context) {
121
+ const userWindows = Math.floor(context.queues.user.length / MODEL_WINDOW_SAMPLES);
122
+ const assistantWindows = Math.floor(context.queues.assistant.length / MODEL_WINDOW_SAMPLES);
123
+ return (userWindows > 0 && assistantWindows > 0) || userWindows > 1 || assistantWindows > 1;
124
+ }
125
+ async inferWindow(context, userAudio, assistantAudio) {
126
+ const [userFeatures, assistantFeatures] = await Promise.all([
127
+ this.encodeMimi(userAudio, context.mimi.user),
128
+ this.encodeMimi(assistantAudio, context.mimi.assistant),
129
+ ]);
130
+ const projection = await this.requireSession(this.projectionSession, "projection").run({
131
+ user_features: userFeatures,
132
+ assistant_features: assistantFeatures,
133
+ });
134
+ const inputsEmbeds = requireTensor(projection["inputs_embeds"], "inputs_embeds");
135
+ const hiddenStates = await this.runBackbone(context, inputsEmbeds);
136
+ const outputs = await this.requireSession(this.headsSession, "turn heads").run({
137
+ hidden_states: hiddenStates,
138
+ });
139
+ return mapTurnHeads(outputs);
140
+ }
141
+ async encodeMimi(audio, state) {
142
+ const ort = this.requireOrt();
143
+ const metadata = this.requireMimiMetadata();
144
+ const cpLength = metadata.cache_position_len;
145
+ const cachePosition = new BigInt64Array(cpLength);
146
+ const positionIds = new BigInt64Array(cpLength);
147
+ for (let index = 0; index < cpLength; index += 1) {
148
+ cachePosition[index] = BigInt((state.cachePosition + index) % metadata.max_past_len);
149
+ positionIds[index] = BigInt(state.position + index);
150
+ }
151
+ const feeds = {
152
+ wave_24k: new ort.Tensor("float32", audio, [1, 1, MODEL_WINDOW_SAMPLES]),
153
+ cache_position: new ort.Tensor("int64", cachePosition, [cpLength]),
154
+ position_ids: new ort.Tensor("int64", positionIds, [cpLength]),
155
+ };
156
+ const paddingCount = metadata.contract.spec.padding_cache_shapes.length;
157
+ for (let index = 0; index < state.states.length; index += 1) {
158
+ const name = index < paddingCount
159
+ ? `pad_cache_${String(index)}`
160
+ : `past_${String(index - paddingCount)}`;
161
+ feeds[name] = state.states[index];
162
+ }
163
+ const output = await this.requireSession(this.mimiSession, "Mimi").run(feeds);
164
+ const cpOut = requireTensor(output["cache_position_out"], "cache_position_out");
165
+ const lastCachePosition = readLastInteger(cpOut);
166
+ state.cachePosition = lastCachePosition ?? ((state.cachePosition + cpLength) % metadata.max_past_len);
167
+ state.position += cpLength;
168
+ state.states = [
169
+ ...Array.from({ length: paddingCount }, (_, index) => requireTensor(output[`pad_cache_out_${String(index)}`], `pad_cache_out_${String(index)}`)),
170
+ ...Array.from({ length: metadata.contract.spec.past_key_value_shapes.length }, (_, index) => requireTensor(output[`past_out_${String(index)}`], `past_out_${String(index)}`)),
171
+ ];
172
+ return normalizeMimiFeatures(requireTensor(output["embeddings"], "embeddings"), ort);
173
+ }
174
+ async runBackbone(context, inputsEmbeds) {
175
+ const ort = this.requireOrt();
176
+ const session = this.requireSession(this.backboneSession, "backbone");
177
+ const timeSteps = inputsEmbeds.dims[1] ?? 0;
178
+ const pastLength = context.backbonePast[0]?.dims[2] ?? 0;
179
+ const feeds = {
180
+ inputs_embeds: inputsEmbeds,
181
+ attention_mask: new ort.Tensor("int64", filledBigInt(pastLength + timeSteps, 1n), [1, pastLength + timeSteps]),
182
+ position_ids: new ort.Tensor("int64", rangeBigInt(context.backbonePosition, timeSteps), [1, timeSteps]),
183
+ };
184
+ for (let layer = 0; layer < 24; layer += 1) {
185
+ const key = context.backbonePast[layer * 2]
186
+ ?? new ort.Tensor("float32", new Float32Array(0), [1, 2, 0, 64]);
187
+ const value = context.backbonePast[layer * 2 + 1]
188
+ ?? new ort.Tensor("float32", new Float32Array(0), [1, 2, 0, 64]);
189
+ feeds[`past_key_values.${String(layer)}.key`] = key;
190
+ feeds[`past_key_values.${String(layer)}.value`] = value;
191
+ }
192
+ const outputs = await session.run(feeds);
193
+ const hidden = requireTensor(outputs[session.outputNames[0]], "backbone hidden states");
194
+ context.backbonePast = session.outputNames.slice(1).map((name) => trimQwenCache(requireTensor(outputs[name], name), this.requireManifest().context_frames, ort));
195
+ context.backbonePosition += timeSteps;
196
+ return hidden;
197
+ }
198
+ assertInitialized() {
199
+ this.requireOrt();
200
+ this.requireManifest();
201
+ this.requireMimiMetadata();
202
+ this.requireSession(this.mimiSession, "Mimi");
203
+ this.requireSession(this.projectionSession, "projection");
204
+ this.requireSession(this.backboneSession, "backbone");
205
+ this.requireSession(this.headsSession, "turn heads");
206
+ }
207
+ requireOrt() {
208
+ if (!this.ort)
209
+ throw new Error("LocalVapPredictor is not initialized");
210
+ return this.ort;
211
+ }
212
+ requireManifest() {
213
+ if (!this.manifest)
214
+ throw new Error("LocalVapPredictor bundle manifest is not loaded");
215
+ return this.manifest;
216
+ }
217
+ requireMimiMetadata() {
218
+ if (!this.mimiMetadata)
219
+ throw new Error("LocalVapPredictor Mimi metadata is not loaded");
220
+ return this.mimiMetadata;
221
+ }
222
+ requireSession(session, label) {
223
+ if (!session)
224
+ throw new Error(`LocalVapPredictor ${label} session is not loaded`);
225
+ return session;
226
+ }
227
+ }
228
+ class PcmSampleQueue {
229
+ storage;
230
+ readIndex = 0;
231
+ writeIndex = 0;
232
+ sampleCount = 0;
233
+ constructor(capacity) {
234
+ this.storage = new Int16Array(capacity);
235
+ }
236
+ get length() {
237
+ return this.sampleCount;
238
+ }
239
+ append(audio) {
240
+ if (audio.length > this.storage.length - this.sampleCount) {
241
+ throw new Error("DualTurn audio queue overflowed while inference was backlogged");
242
+ }
243
+ for (const sample of audio) {
244
+ this.storage[this.writeIndex] = sample;
245
+ this.writeIndex = (this.writeIndex + 1) % this.storage.length;
246
+ this.sampleCount += 1;
247
+ }
248
+ }
249
+ drainNormalized(count) {
250
+ if (count > this.sampleCount)
251
+ throw new Error("DualTurn audio queue underflow");
252
+ const output = new Float32Array(count);
253
+ for (let index = 0; index < count; index += 1) {
254
+ output[index] = this.storage[this.readIndex] / 32_768;
255
+ this.readIndex = (this.readIndex + 1) % this.storage.length;
256
+ this.sampleCount -= 1;
257
+ }
258
+ return output;
259
+ }
260
+ }
261
+ async function readJson(path) {
262
+ return JSON.parse(await readFile(path, "utf8"));
263
+ }
264
+ function parseManifest(value) {
265
+ const manifest = value;
266
+ if (manifest.format !== "syrinx.dualturn.onnx.v1" || !manifest.files || !manifest.context_frames) {
267
+ throw new Error("Unsupported or invalid DualTurn bundle manifest");
268
+ }
269
+ return manifest;
270
+ }
271
+ function parseMimiMetadata(value) {
272
+ const metadata = value;
273
+ if (!metadata.cache_position_len || !metadata.max_past_len || !metadata.contract?.spec) {
274
+ throw new Error("Invalid Continuous Mimi ONNX metadata");
275
+ }
276
+ return metadata;
277
+ }
278
+ function product(shape) {
279
+ return shape.reduce((size, dimension) => size * dimension, 1);
280
+ }
281
+ function requireTensor(tensor, name) {
282
+ if (!tensor)
283
+ throw new Error(`ONNX output ${name} is missing`);
284
+ return tensor;
285
+ }
286
+ function readLastInteger(tensor) {
287
+ const data = tensor.data;
288
+ const value = data[data.length - 1];
289
+ if (typeof value === "bigint")
290
+ return Number(value);
291
+ if (typeof value === "number" && Number.isFinite(value))
292
+ return Math.trunc(value);
293
+ return undefined;
294
+ }
295
+ function normalizeMimiFeatures(tensor, ort) {
296
+ const [batch = 0, first = 0, second = 0] = tensor.dims;
297
+ if (batch !== 1)
298
+ throw new Error(`Mimi emitted unsupported batch size ${String(batch)}`);
299
+ if (second === 512)
300
+ return tensor;
301
+ if (first !== 512) {
302
+ throw new Error(`Mimi emitted unsupported feature shape [${tensor.dims.join(",")}]`);
303
+ }
304
+ const source = tensor.data;
305
+ const output = new Float32Array(second * first);
306
+ for (let frame = 0; frame < second; frame += 1) {
307
+ for (let feature = 0; feature < first; feature += 1) {
308
+ output[frame * first + feature] = source[feature * second + frame];
309
+ }
310
+ }
311
+ return new ort.Tensor("float32", output, [1, second, first]);
312
+ }
313
+ function rangeBigInt(start, length) {
314
+ const output = new BigInt64Array(length);
315
+ for (let index = 0; index < length; index += 1)
316
+ output[index] = BigInt(start + index);
317
+ return output;
318
+ }
319
+ function filledBigInt(length, value) {
320
+ const output = new BigInt64Array(length);
321
+ output.fill(value);
322
+ return output;
323
+ }
324
+ function trimQwenCache(tensor, maxFrames, ort) {
325
+ const [batch = 0, heads = 0, frames = 0, headSize = 0] = tensor.dims;
326
+ if (frames <= maxFrames)
327
+ return tensor;
328
+ if (batch !== 1)
329
+ throw new Error("DualTurn Qwen cache only supports batch size 1");
330
+ const source = tensor.data;
331
+ const output = new Float32Array(heads * maxFrames * headSize);
332
+ for (let head = 0; head < heads; head += 1) {
333
+ const sourceStart = (head * frames + frames - maxFrames) * headSize;
334
+ const destinationStart = head * maxFrames * headSize;
335
+ output.set(source.subarray(sourceStart, sourceStart + maxFrames * headSize), destinationStart);
336
+ }
337
+ return new ort.Tensor("float32", output, [1, heads, maxFrames, headSize]);
338
+ }
339
+ function mapTurnHeads(output) {
340
+ const eot = requireFloatOutput(output["eot_probs"], "eot_probs");
341
+ const bot = requireFloatOutput(output["bot_probs"], "bot_probs");
342
+ const hold = requireFloatOutput(output["hold_probs"], "hold_probs");
343
+ const backchannel = requireFloatOutput(output["bc_probs"], "bc_probs");
344
+ const latestUserIndex = eot.length - 2;
345
+ return {
346
+ pShift: Math.max(eot[latestUserIndex] ?? 0, bot[latestUserIndex] ?? 0),
347
+ pBackchannel: backchannel[backchannel.length - 2] ?? 0,
348
+ pHold: hold[hold.length - 2] ?? 0,
349
+ };
350
+ }
351
+ function requireFloatOutput(tensor, name) {
352
+ const value = requireTensor(tensor, name).data;
353
+ if (!(value instanceof Float32Array))
354
+ throw new Error(`ONNX output ${name} must be float32`);
355
+ return value;
356
+ }
357
+ //# sourceMappingURL=dualturn-predictor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dualturn-predictor.js","sourceRoot":"","sources":["../src/dualturn-predictor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { PluginConfig } from "@kuralle-syrinx/core";
2
+ import type { AcousticSignalSink, InteractionDecision, InteractionObservation, InteractionPolicy } from "@kuralle-syrinx/core";
3
+ import { type VapPredictor, type VapPredictorFrame, type VapProbs, type VapThresholds, type VapTranscriptFusion } from "./vap-policy.js";
4
+ export type { VapPredictor, VapPredictorFrame, VapProbs, VapThresholds, VapDecisionContext, VapTranscriptFusion, VapTranscriptObservation, } from "./vap-policy.js";
5
+ export { DEFAULT_VAP_THRESHOLDS, ZERO_VAP_PROBS, decideFromProbs, RollingFeatureBuffer, semanticTranscriptFusion, } from "./vap-policy.js";
6
+ export { LocalVapPredictor } from "./dualturn-predictor.js";
7
+ export declare class StubVapPredictor implements VapPredictor {
8
+ private scripted;
9
+ private fallback;
10
+ script(probs: readonly VapProbs[]): void;
11
+ setFallback(probs: VapProbs): void;
12
+ initialize(_cfg?: PluginConfig): Promise<void>;
13
+ push(_frame: VapPredictorFrame): Promise<VapProbs>;
14
+ reset(_contextId: string): void;
15
+ close(): Promise<void>;
16
+ }
17
+ export interface VapInteractionPolicyDeps {
18
+ readonly predictor: VapPredictor;
19
+ readonly thresholds?: VapThresholds;
20
+ readonly fuseTranscript?: VapTranscriptFusion;
21
+ }
22
+ export declare class VapInteractionPolicy implements InteractionPolicy {
23
+ private readonly predictor;
24
+ private readonly thresholds;
25
+ private readonly fuseTranscript?;
26
+ private readonly cachedProbs;
27
+ private readonly transcripts;
28
+ private readonly inferenceChains;
29
+ private readonly contextEpochs;
30
+ private ttsActive;
31
+ private ttsContextId;
32
+ private delegateInFlight;
33
+ private initialized;
34
+ private acousticSignalSink;
35
+ constructor(deps: VapInteractionPolicyDeps);
36
+ initialize(cfg?: PluginConfig): Promise<void>;
37
+ setAcousticSignalSink(sink: AcousticSignalSink): void;
38
+ observe(obs: InteractionObservation): readonly InteractionDecision[];
39
+ reset(contextId: string): void;
40
+ close(): Promise<void>;
41
+ private observeAudioFrame;
42
+ private decisionContext;
43
+ private enqueueInference;
44
+ }
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,mBAAmB,EAEzB,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,aAAa,EACb,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAA4B;IAE5C,MAAM,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,IAAI;IAIxC,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;IAI5B,UAAU,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAElD,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAIxD,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAEzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,mBAAmB,CAAC;CAC/C;AAED,qBAAa,oBAAqB,YAAW,iBAAiB;IAC5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAgB;IAC3C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAsB;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAC3D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+C;IAC3E,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoC;IACpE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6B;IAC3D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,kBAAkB,CAAiC;gBAE/C,IAAI,EAAE,wBAAwB;IAMpC,UAAU,CAAC,GAAG,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKvD,qBAAqB,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI;IAIrD,OAAO,CAAC,GAAG,EAAE,sBAAsB,GAAG,SAAS,mBAAmB,EAAE;IA6BpE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAWxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,OAAO,CAAC,iBAAiB;IAqBzB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,gBAAgB;CAkCzB"}
package/dist/index.js ADDED
@@ -0,0 +1,158 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import { DEFAULT_VAP_THRESHOLDS, ZERO_VAP_PROBS, decideFromProbs, } from "./vap-policy.js";
3
+ export { DEFAULT_VAP_THRESHOLDS, ZERO_VAP_PROBS, decideFromProbs, RollingFeatureBuffer, semanticTranscriptFusion, } from "./vap-policy.js";
4
+ export { LocalVapPredictor } from "./dualturn-predictor.js";
5
+ export class StubVapPredictor {
6
+ scripted = [];
7
+ fallback = ZERO_VAP_PROBS;
8
+ script(probs) {
9
+ this.scripted = [...probs];
10
+ }
11
+ setFallback(probs) {
12
+ this.fallback = probs;
13
+ }
14
+ async initialize(_cfg = {}) { }
15
+ async push(_frame) {
16
+ return this.scripted.shift() ?? this.fallback;
17
+ }
18
+ reset(_contextId) { }
19
+ async close() { }
20
+ }
21
+ export class VapInteractionPolicy {
22
+ predictor;
23
+ thresholds;
24
+ fuseTranscript;
25
+ cachedProbs = new Map();
26
+ transcripts = new Map();
27
+ inferenceChains = new Map();
28
+ contextEpochs = new Map();
29
+ ttsActive = false;
30
+ ttsContextId = "";
31
+ delegateInFlight = false;
32
+ initialized = false;
33
+ acousticSignalSink;
34
+ constructor(deps) {
35
+ this.predictor = deps.predictor;
36
+ this.thresholds = deps.thresholds ?? DEFAULT_VAP_THRESHOLDS;
37
+ this.fuseTranscript = deps.fuseTranscript;
38
+ }
39
+ async initialize(cfg = {}) {
40
+ await this.predictor.initialize(cfg);
41
+ this.initialized = true;
42
+ }
43
+ setAcousticSignalSink(sink) {
44
+ this.acousticSignalSink = sink;
45
+ }
46
+ observe(obs) {
47
+ switch (obs.kind) {
48
+ case "playout_tick":
49
+ this.ttsActive = obs.ttsActive === true;
50
+ if (this.ttsActive)
51
+ this.ttsContextId = obs.contextId;
52
+ if (obs.audio && obs.audio.length > 0) {
53
+ this.enqueueInference({
54
+ contextId: obs.contextId,
55
+ timestampMs: obs.timestampMs,
56
+ channel: "assistant",
57
+ audio: obs.audio,
58
+ sampleRateHz: obs.sampleRateHz ?? 16_000,
59
+ });
60
+ }
61
+ return [];
62
+ case "delegate_state":
63
+ this.delegateInFlight = obs.delegateInFlight === true;
64
+ return [];
65
+ case "stt_partial":
66
+ case "stt_final":
67
+ this.transcripts.set(obs.contextId, obs);
68
+ return [];
69
+ case "audio_frame":
70
+ return this.observeAudioFrame(obs);
71
+ default:
72
+ return [];
73
+ }
74
+ }
75
+ reset(contextId) {
76
+ if (this.inferenceChains.has(contextId)) {
77
+ this.contextEpochs.set(contextId, (this.contextEpochs.get(contextId) ?? 0) + 1);
78
+ }
79
+ else {
80
+ this.contextEpochs.delete(contextId);
81
+ }
82
+ this.cachedProbs.delete(contextId);
83
+ this.transcripts.delete(contextId);
84
+ this.predictor.reset(contextId);
85
+ }
86
+ async close() {
87
+ await Promise.allSettled(this.inferenceChains.values());
88
+ await this.predictor.close();
89
+ this.cachedProbs.clear();
90
+ this.transcripts.clear();
91
+ this.inferenceChains.clear();
92
+ this.contextEpochs.clear();
93
+ this.initialized = false;
94
+ this.acousticSignalSink = undefined;
95
+ }
96
+ observeAudioFrame(obs) {
97
+ if (!this.initialized)
98
+ return [{ kind: "keep_listening" }];
99
+ if (obs.audio && obs.audio.length > 0) {
100
+ this.enqueueInference({
101
+ contextId: obs.contextId,
102
+ timestampMs: obs.timestampMs,
103
+ channel: "user",
104
+ audio: obs.audio,
105
+ sampleRateHz: obs.sampleRateHz ?? 16_000,
106
+ });
107
+ }
108
+ const rawProbs = this.cachedProbs.get(obs.contextId) ?? ZERO_VAP_PROBS;
109
+ const transcript = this.transcripts.get(obs.contextId);
110
+ const probs = transcript && this.fuseTranscript
111
+ ? this.fuseTranscript(rawProbs, transcript)
112
+ : rawProbs;
113
+ return decideFromProbs(probs, this.decisionContext());
114
+ }
115
+ decisionContext() {
116
+ return {
117
+ ttsActive: this.ttsActive,
118
+ ttsContextId: this.ttsContextId,
119
+ delegateInFlight: this.delegateInFlight,
120
+ };
121
+ }
122
+ enqueueInference(frame) {
123
+ const stableFrame = { ...frame, audio: frame.audio.slice() };
124
+ const epoch = this.contextEpochs.get(frame.contextId) ?? 0;
125
+ const previous = this.inferenceChains.get(frame.contextId) ?? Promise.resolve();
126
+ const next = previous
127
+ .catch(() => undefined)
128
+ .then(async () => {
129
+ if ((this.contextEpochs.get(frame.contextId) ?? 0) !== epoch)
130
+ return;
131
+ const probs = await this.predictor.push(stableFrame);
132
+ if ((this.contextEpochs.get(frame.contextId) ?? 0) === epoch) {
133
+ this.cachedProbs.set(frame.contextId, probs);
134
+ if (this.initialized) {
135
+ this.acousticSignalSink?.({
136
+ contextId: frame.contextId,
137
+ timestampMs: frame.timestampMs,
138
+ signal: "prosody",
139
+ payload: {
140
+ channel: frame.channel,
141
+ pShift: probs.pShift,
142
+ pBackchannel: probs.pBackchannel,
143
+ pHold: probs.pHold,
144
+ },
145
+ });
146
+ }
147
+ }
148
+ });
149
+ const settled = next.catch(() => undefined).finally(() => {
150
+ if (this.inferenceChains.get(frame.contextId) === settled) {
151
+ this.inferenceChains.delete(frame.contextId);
152
+ this.contextEpochs.delete(frame.contextId);
153
+ }
154
+ });
155
+ this.inferenceChains.set(frame.contextId, settled);
156
+ }
157
+ }
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import type { InteractionDecision, InteractionObservation, PluginConfig } from "@kuralle-syrinx/core";
2
+ export interface VapPredictor {
3
+ initialize(cfg: PluginConfig): Promise<void>;
4
+ push(frame: VapPredictorFrame): Promise<VapProbs>;
5
+ reset(contextId: string): void;
6
+ close(): Promise<void>;
7
+ }
8
+ export interface VapPredictorFrame {
9
+ readonly contextId: string;
10
+ readonly timestampMs: number;
11
+ readonly channel: "user" | "assistant";
12
+ readonly audio: Int16Array;
13
+ readonly sampleRateHz: number;
14
+ }
15
+ export interface VapProbs {
16
+ readonly pShift: number;
17
+ readonly pBackchannel: number;
18
+ readonly pHold: number;
19
+ }
20
+ export type VapTranscriptObservation = Extract<InteractionObservation, {
21
+ kind: "stt_partial" | "stt_final";
22
+ }>;
23
+ export type VapTranscriptFusion = (probs: VapProbs, transcript: VapTranscriptObservation) => VapProbs;
24
+ export interface VapThresholds {
25
+ readonly shift: number;
26
+ readonly backchannel: number;
27
+ readonly take: number;
28
+ readonly hold: number;
29
+ }
30
+ export declare const DEFAULT_VAP_THRESHOLDS: VapThresholds;
31
+ export declare const ZERO_VAP_PROBS: VapProbs;
32
+ export interface VapDecisionContext {
33
+ readonly ttsActive: boolean;
34
+ readonly ttsContextId: string;
35
+ readonly delegateInFlight: boolean;
36
+ }
37
+ export declare function decideFromProbs(probs: VapProbs, ctx: VapDecisionContext, thresholds?: VapThresholds): readonly InteractionDecision[];
38
+ /** Conservative transcript evidence used by the C6 VAP+STT arm. */
39
+ export declare function semanticTranscriptFusion(probs: VapProbs, transcript: VapTranscriptObservation): VapProbs;
40
+ export declare class RollingFeatureBuffer {
41
+ private readonly maxSamples;
42
+ private readonly storage;
43
+ private length;
44
+ private writeIndex;
45
+ constructor(maxSamples?: number);
46
+ append(audio?: Int16Array): void;
47
+ /** Returns an immutable chronological copy suitable for async inference. */
48
+ snapshot(): Float32Array;
49
+ reset(): void;
50
+ }
51
+ //# sourceMappingURL=vap-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vap-policy.d.ts","sourceRoot":"","sources":["../src/vap-policy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEtG,MAAM,WAAW,YAAY;IAC3B,UAAU,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClD,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,wBAAwB,GAAG,OAAO,CAC5C,sBAAsB,EACtB;IAAE,IAAI,EAAE,aAAa,GAAG,WAAW,CAAA;CAAE,CACtC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,wBAAwB,KACjC,QAAQ,CAAC;AAEd,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,sBAAsB,EAAE,aAKpC,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,QAAmD,CAAC;AAEjF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACpC;AAED,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,kBAAkB,EACvB,UAAU,GAAE,aAAsC,GACjD,SAAS,mBAAmB,EAAE,CAchC;AAED,mEAAmE;AACnE,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,QAAQ,EACf,UAAU,EAAE,wBAAwB,GACnC,QAAQ,CAQV;AAID,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,UAAU,CAAK;gBAEM,UAAU,SAAsB;IAI7D,MAAM,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,IAAI;IAWhC,4EAA4E;IAC5E,QAAQ,IAAI,YAAY;IAWxB,KAAK,IAAI,IAAI;CAId"}
@@ -0,0 +1,73 @@
1
+ // SPDX-License-Identifier: MIT
2
+ export const DEFAULT_VAP_THRESHOLDS = {
3
+ shift: 0.75,
4
+ backchannel: 0.5,
5
+ take: 0.6,
6
+ hold: 0.5,
7
+ };
8
+ export const ZERO_VAP_PROBS = { pShift: 0, pBackchannel: 0, pHold: 0 };
9
+ export function decideFromProbs(probs, ctx, thresholds = DEFAULT_VAP_THRESHOLDS) {
10
+ if (ctx.ttsActive && probs.pShift > thresholds.shift) {
11
+ return [{ kind: "interrupt", interruptedContextId: ctx.ttsContextId }];
12
+ }
13
+ if (probs.pBackchannel > thresholds.backchannel && ctx.delegateInFlight) {
14
+ return [{ kind: "backchannel", cue: "mhmm" }];
15
+ }
16
+ if (probs.pShift > thresholds.take) {
17
+ return [{ kind: "take_turn", confidence: probs.pShift }];
18
+ }
19
+ if (probs.pHold > thresholds.hold) {
20
+ return [{ kind: "hold" }];
21
+ }
22
+ return [{ kind: "keep_listening" }];
23
+ }
24
+ /** Conservative transcript evidence used by the C6 VAP+STT arm. */
25
+ export function semanticTranscriptFusion(probs, transcript) {
26
+ const text = transcript.text.trim().replace(/\s+/g, " ");
27
+ if (!text)
28
+ return probs;
29
+ const incomplete = /\b(and|but|or|so|because|if|when|while|although|since|unless|until|the|a|an|to|for|of|in|on|at|with|about|from|by|please|just|also|then|well|um|uh|i|we|you|my|your|our|this)$/i.test(text)
30
+ || text.endsWith(",");
31
+ if (incomplete)
32
+ return { ...probs, pShift: Math.min(probs.pShift, 0.49) };
33
+ if (/[.!?]["')]*$/.test(text))
34
+ return { ...probs, pShift: Math.max(probs.pShift, 0.9) };
35
+ return probs;
36
+ }
37
+ const DEFAULT_MAX_SAMPLES = 16_000;
38
+ export class RollingFeatureBuffer {
39
+ maxSamples;
40
+ storage;
41
+ length = 0;
42
+ writeIndex = 0;
43
+ constructor(maxSamples = DEFAULT_MAX_SAMPLES) {
44
+ this.maxSamples = maxSamples;
45
+ this.storage = new Float32Array(maxSamples);
46
+ }
47
+ append(audio) {
48
+ if (audio && audio.length > 0) {
49
+ for (let i = 0; i < audio.length; i += 1) {
50
+ const sample = audio[i] ?? 0;
51
+ this.storage[this.writeIndex] = sample / 32768;
52
+ this.writeIndex = (this.writeIndex + 1) % this.maxSamples;
53
+ this.length = Math.min(this.length + 1, this.maxSamples);
54
+ }
55
+ }
56
+ }
57
+ /** Returns an immutable chronological copy suitable for async inference. */
58
+ snapshot() {
59
+ const snapshot = new Float32Array(this.length);
60
+ const start = (this.writeIndex - this.length + this.maxSamples) % this.maxSamples;
61
+ const firstLength = Math.min(this.length, this.maxSamples - start);
62
+ snapshot.set(this.storage.subarray(start, start + firstLength));
63
+ if (firstLength < this.length) {
64
+ snapshot.set(this.storage.subarray(0, this.length - firstLength), firstLength);
65
+ }
66
+ return snapshot;
67
+ }
68
+ reset() {
69
+ this.length = 0;
70
+ this.writeIndex = 0;
71
+ }
72
+ }
73
+ //# sourceMappingURL=vap-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vap-policy.js","sourceRoot":"","sources":["../src/vap-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { PluginConfig } from "@kuralle-syrinx/core";
2
+ import { type VapPredictor, type VapPredictorFrame, type VapProbs } from "./vap-policy.js";
3
+ export declare class WorkersVapPredictor implements VapPredictor {
4
+ private ort;
5
+ private session;
6
+ initialize(cfg?: PluginConfig): Promise<void>;
7
+ private readonly buffers;
8
+ private readonly cachedProbs;
9
+ push(frame: VapPredictorFrame): Promise<VapProbs>;
10
+ reset(contextId: string): void;
11
+ close(): Promise<void>;
12
+ private bufferFor;
13
+ }
14
+ //# sourceMappingURL=workers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../src/workers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,QAAQ,EACd,MAAM,iBAAiB,CAAC;AAKzB,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,OAAO,CAAiC;IAE1C,UAAU,CAAC,GAAG,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2C;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA+B;IAErD,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAkBvD,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,OAAO,CAAC,SAAS;CAQlB"}
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import { optionalStringConfig } from "@kuralle-syrinx/core";
3
+ import { RollingFeatureBuffer, ZERO_VAP_PROBS, } from "./vap-policy.js";
4
+ export class WorkersVapPredictor {
5
+ ort = null;
6
+ session = null;
7
+ async initialize(cfg = {}) {
8
+ this.ort = await import("onnxruntime-web");
9
+ this.session = await this.ort.InferenceSession.create(await readModelBytes(cfg), {
10
+ executionProviders: ["wasm"],
11
+ });
12
+ }
13
+ buffers = new Map();
14
+ cachedProbs = new Map();
15
+ async push(frame) {
16
+ if (!this.session || !this.ort) {
17
+ throw new Error("WorkersVapPredictor is not initialized");
18
+ }
19
+ if (frame.channel === "assistant") {
20
+ return this.cachedProbs.get(frame.contextId) ?? ZERO_VAP_PROBS;
21
+ }
22
+ const buffer = this.bufferFor(frame.contextId);
23
+ buffer.append(frame.audio);
24
+ const features = buffer.snapshot();
25
+ const output = await this.session.run({
26
+ features: new this.ort.Tensor("float32", features, [1, features.length]),
27
+ });
28
+ const probs = mapOnnxOutput(output);
29
+ this.cachedProbs.set(frame.contextId, probs);
30
+ return probs;
31
+ }
32
+ reset(contextId) {
33
+ this.buffers.delete(contextId);
34
+ this.cachedProbs.delete(contextId);
35
+ }
36
+ async close() {
37
+ this.session = null;
38
+ this.ort = null;
39
+ this.buffers.clear();
40
+ this.cachedProbs.clear();
41
+ }
42
+ bufferFor(contextId) {
43
+ let buffer = this.buffers.get(contextId);
44
+ if (!buffer) {
45
+ buffer = new RollingFeatureBuffer();
46
+ this.buffers.set(contextId, buffer);
47
+ }
48
+ return buffer;
49
+ }
50
+ }
51
+ async function readModelBytes(cfg) {
52
+ const bytes = cfg["model_bytes"];
53
+ if (bytes instanceof Uint8Array)
54
+ return bytes;
55
+ if (bytes instanceof ArrayBuffer)
56
+ return new Uint8Array(bytes);
57
+ const url = optionalStringConfig(cfg, "model_url");
58
+ if (!url) {
59
+ throw new Error("WorkersVapPredictor requires model_bytes or model_url");
60
+ }
61
+ const response = await fetch(url);
62
+ if (!response.ok) {
63
+ throw new Error(`Failed to fetch VAP model from ${url}: ${response.status}`);
64
+ }
65
+ return new Uint8Array(await response.arrayBuffer());
66
+ }
67
+ function mapOnnxOutput(output) {
68
+ const shift = readScalar(output["p_shift"] ?? output["pShift"] ?? output["shift"]);
69
+ const backchannel = readScalar(output["p_backchannel"] ?? output["pBackchannel"] ?? output["backchannel"]);
70
+ const hold = readScalar(output["p_hold"] ?? output["pHold"] ?? output["hold"]);
71
+ return { pShift: shift, pBackchannel: backchannel, pHold: hold };
72
+ }
73
+ function readScalar(tensor) {
74
+ const value = tensor?.data?.[0];
75
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
76
+ }
77
+ //# sourceMappingURL=workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workers.js","sourceRoot":"","sources":["../src/workers.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuralle-syrinx/vap",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "private": false,
5
5
  "description": "Voice Activity Projection interaction policy for Syrinx — ONNX turn-taking for Node and Workers",
6
6
  "keywords": [
@@ -23,23 +23,36 @@
23
23
  "url": "https://github.com/kuralle/syrinx/issues"
24
24
  },
25
25
  "type": "module",
26
- "main": "./src/index.ts",
27
- "types": "./src/index.ts",
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
28
  "exports": {
29
- ".": "./src/index.ts",
30
- "./workers": "./src/workers.ts"
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "default": "./dist/index.js"
32
+ },
33
+ "./workers": {
34
+ "types": "./dist/workers.d.ts",
35
+ "default": "./dist/workers.js"
36
+ }
31
37
  },
32
38
  "dependencies": {
33
39
  "onnxruntime-node": "1.24.3",
34
40
  "onnxruntime-web": "1.24.3",
35
- "@kuralle-syrinx/core": "4.4.0"
41
+ "@kuralle-syrinx/core": "4.5.0"
36
42
  },
37
43
  "devDependencies": {
44
+ "@types/node": "^22.0.0",
38
45
  "typescript": "^5.7.0",
39
46
  "vitest": "^3.2.6"
40
47
  },
48
+ "files": [
49
+ "dist",
50
+ "src",
51
+ "README.md"
52
+ ],
41
53
  "scripts": {
42
54
  "typecheck": "tsc --noEmit",
43
- "test": "vitest run"
55
+ "test": "vitest run",
56
+ "build": "tsc -p tsconfig.build.json"
44
57
  }
45
58
  }
@@ -0,0 +1,341 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ import {
6
+ DEFAULT_VAP_THRESHOLDS,
7
+ RollingFeatureBuffer,
8
+ decideFromProbs,
9
+ semanticTranscriptFusion,
10
+ type VapPredictor,
11
+ type VapPredictorFrame,
12
+ type VapProbs,
13
+ } from "./vap-policy.js";
14
+ import { StubVapPredictor, VapInteractionPolicy } from "./index.js";
15
+
16
+ function audioFrame(contextId = "ctx-1", audio = new Int16Array(320)) {
17
+ return {
18
+ kind: "audio_frame" as const,
19
+ contextId,
20
+ timestampMs: Date.now(),
21
+ audio,
22
+ sampleRateHz: 16_000,
23
+ };
24
+ }
25
+
26
+ async function flushInference(): Promise<void> {
27
+ await new Promise((resolve) => setTimeout(resolve, 0));
28
+ }
29
+
30
+ describe("decideFromProbs", () => {
31
+ it("maps each threshold path to the expected InteractionDecision", () => {
32
+ const thresholds = DEFAULT_VAP_THRESHOLDS;
33
+ expect(
34
+ decideFromProbs(
35
+ { pShift: thresholds.shift + 0.1, pBackchannel: 0, pHold: 0 },
36
+ { ttsActive: true, ttsContextId: "tts-1", delegateInFlight: false },
37
+ thresholds,
38
+ ),
39
+ ).toEqual([{ kind: "interrupt", interruptedContextId: "tts-1" }]);
40
+
41
+ expect(
42
+ decideFromProbs(
43
+ { pShift: 0, pBackchannel: thresholds.backchannel + 0.1, pHold: 0 },
44
+ { ttsActive: false, ttsContextId: "", delegateInFlight: true },
45
+ thresholds,
46
+ ),
47
+ ).toEqual([{ kind: "backchannel", cue: "mhmm" }]);
48
+
49
+ expect(
50
+ decideFromProbs(
51
+ { pShift: thresholds.take + 0.1, pBackchannel: 0, pHold: 0 },
52
+ { ttsActive: false, ttsContextId: "", delegateInFlight: false },
53
+ thresholds,
54
+ ),
55
+ ).toEqual([{ kind: "take_turn", confidence: thresholds.take + 0.1 }]);
56
+
57
+ expect(
58
+ decideFromProbs(
59
+ { pShift: 0, pBackchannel: 0, pHold: thresholds.hold + 0.1 },
60
+ { ttsActive: false, ttsContextId: "", delegateInFlight: false },
61
+ thresholds,
62
+ ),
63
+ ).toEqual([{ kind: "hold" }]);
64
+
65
+ expect(
66
+ decideFromProbs(
67
+ { pShift: 0.1, pBackchannel: 0.1, pHold: 0.1 },
68
+ { ttsActive: false, ttsContextId: "", delegateInFlight: false },
69
+ thresholds,
70
+ ),
71
+ ).toEqual([{ kind: "keep_listening" }]);
72
+ });
73
+ });
74
+
75
+ describe("VapInteractionPolicy", () => {
76
+ it("does not emit prosody while VAP is dormant", async () => {
77
+ const policy = new VapInteractionPolicy({ predictor: new StubVapPredictor() });
78
+ const sink = vi.fn();
79
+ policy.setAcousticSignalSink(sink);
80
+
81
+ expect(() => policy.observe(audioFrame())).not.toThrow();
82
+ await flushInference();
83
+
84
+ expect(sink).not.toHaveBeenCalled();
85
+ });
86
+
87
+ it("emits prosody only after initialized predictor output", async () => {
88
+ const predictor = new StubVapPredictor();
89
+ predictor.setFallback({ pShift: 0.4, pBackchannel: 0.2, pHold: 0.1 });
90
+ const policy = new VapInteractionPolicy({ predictor });
91
+ const sink = vi.fn();
92
+ policy.setAcousticSignalSink(sink);
93
+ await policy.initialize();
94
+
95
+ policy.observe(audioFrame("prosody-turn"));
96
+ await flushInference();
97
+
98
+ expect(sink).toHaveBeenCalledWith({
99
+ contextId: "prosody-turn",
100
+ timestampMs: expect.any(Number),
101
+ signal: "prosody",
102
+ payload: {
103
+ channel: "user",
104
+ pShift: 0.4,
105
+ pBackchannel: 0.2,
106
+ pHold: 0.1,
107
+ },
108
+ });
109
+ await policy.close();
110
+ });
111
+
112
+ it("returns cached decisions synchronously from observe (test:vap-thresholds)", async () => {
113
+ const predictor = new StubVapPredictor();
114
+ predictor.script([
115
+ { pShift: DEFAULT_VAP_THRESHOLDS.take + 0.2, pBackchannel: 0, pHold: 0 },
116
+ ]);
117
+ const policy = new VapInteractionPolicy({ predictor });
118
+ await policy.initialize();
119
+
120
+ const first = policy.observe(audioFrame());
121
+ expect(first).toEqual([{ kind: "keep_listening" }]);
122
+
123
+ await flushInference();
124
+ const second = policy.observe(audioFrame());
125
+ expect(second).toEqual([
126
+ { kind: "take_turn", confidence: DEFAULT_VAP_THRESHOLDS.take + 0.2 },
127
+ ]);
128
+ });
129
+
130
+ it("gates interrupt on ttsActive and backchannel on delegateInFlight", async () => {
131
+ const predictor = new StubVapPredictor();
132
+ predictor.setFallback({
133
+ pShift: DEFAULT_VAP_THRESHOLDS.shift + 0.1,
134
+ pBackchannel: 0,
135
+ pHold: 0,
136
+ });
137
+ const policy = new VapInteractionPolicy({ predictor });
138
+ await policy.initialize();
139
+
140
+ policy.observe(audioFrame());
141
+ await flushInference();
142
+ expect(policy.observe(audioFrame())).toEqual([
143
+ { kind: "take_turn", confidence: DEFAULT_VAP_THRESHOLDS.shift + 0.1 },
144
+ ]);
145
+
146
+ policy.observe({
147
+ kind: "playout_tick",
148
+ contextId: "tts-ctx",
149
+ timestampMs: Date.now(),
150
+ ttsActive: true,
151
+ });
152
+ expect(policy.observe(audioFrame())).toEqual([
153
+ { kind: "interrupt", interruptedContextId: "tts-ctx" },
154
+ ]);
155
+
156
+ policy.observe({
157
+ kind: "playout_tick",
158
+ contextId: "tts-ctx",
159
+ timestampMs: Date.now(),
160
+ ttsActive: false,
161
+ });
162
+ predictor.setFallback({
163
+ pShift: 0,
164
+ pBackchannel: DEFAULT_VAP_THRESHOLDS.backchannel + 0.1,
165
+ pHold: 0,
166
+ });
167
+ policy.observe(audioFrame());
168
+ await flushInference();
169
+
170
+ policy.observe({
171
+ kind: "delegate_state",
172
+ contextId: "ctx-1",
173
+ timestampMs: Date.now(),
174
+ delegateInFlight: false,
175
+ });
176
+ expect(policy.observe(audioFrame())).toEqual([{ kind: "keep_listening" }]);
177
+
178
+ policy.observe({
179
+ kind: "delegate_state",
180
+ contextId: "ctx-1",
181
+ timestampMs: Date.now(),
182
+ delegateInFlight: true,
183
+ });
184
+ expect(policy.observe(audioFrame())).toEqual([{ kind: "backchannel", cue: "mhmm" }]);
185
+ });
186
+
187
+ it("serializes stable user and assistant frames and resets predictor state", async () => {
188
+ const pushed: VapPredictorFrame[] = [];
189
+ const resets: string[] = [];
190
+ const predictor: VapPredictor = {
191
+ async initialize() {},
192
+ async push(frame) {
193
+ pushed.push(frame);
194
+ return { pShift: 0, pBackchannel: 0, pHold: 0 };
195
+ },
196
+ reset(contextId) {
197
+ resets.push(contextId);
198
+ },
199
+ async close() {},
200
+ };
201
+ const policy = new VapInteractionPolicy({ predictor });
202
+ await policy.initialize();
203
+ const userAudio = new Int16Array([1, 2]);
204
+ const assistantAudio = new Int16Array([3, 4]);
205
+
206
+ policy.observe(audioFrame("duplex", userAudio));
207
+ policy.observe({
208
+ kind: "playout_tick",
209
+ contextId: "duplex",
210
+ timestampMs: 2,
211
+ ttsActive: true,
212
+ audio: assistantAudio,
213
+ sampleRateHz: 24_000,
214
+ });
215
+ userAudio[0] = 99;
216
+ assistantAudio[0] = 99;
217
+ await flushInference();
218
+
219
+ expect(pushed.map(({ channel, audio, sampleRateHz }) => ({
220
+ channel,
221
+ audio: [...audio],
222
+ sampleRateHz,
223
+ }))).toEqual([
224
+ { channel: "user", audio: [1, 2], sampleRateHz: 16_000 },
225
+ { channel: "assistant", audio: [3, 4], sampleRateHz: 24_000 },
226
+ ]);
227
+
228
+ policy.reset("duplex");
229
+ expect(resets).toEqual(["duplex"]);
230
+ });
231
+
232
+ it("does not run queued frames across reset and drains inference before close", async () => {
233
+ let releaseFirst!: () => void;
234
+ const firstPush = new Promise<void>((resolve) => {
235
+ releaseFirst = resolve;
236
+ });
237
+ const pushed: number[] = [];
238
+ let closed = false;
239
+ const predictor: VapPredictor = {
240
+ async initialize() {},
241
+ async push(frame) {
242
+ pushed.push(frame.audio[0] ?? 0);
243
+ if (pushed.length === 1) await firstPush;
244
+ return { pShift: 0, pBackchannel: 0, pHold: 0 };
245
+ },
246
+ reset() {},
247
+ async close() {
248
+ closed = true;
249
+ },
250
+ };
251
+ const policy = new VapInteractionPolicy({ predictor });
252
+ await policy.initialize();
253
+
254
+ policy.observe(audioFrame("reset", new Int16Array([1])));
255
+ policy.observe(audioFrame("reset", new Int16Array([2])));
256
+ await flushInference();
257
+ policy.reset("reset");
258
+ const closing = policy.close();
259
+
260
+ expect(closed).toBe(false);
261
+ releaseFirst();
262
+ await closing;
263
+ expect(pushed).toEqual([1]);
264
+ expect(closed).toBe(true);
265
+ });
266
+
267
+ it("can fuse per-context transcript evidence into cached VAP probabilities", async () => {
268
+ const predictor = new StubVapPredictor();
269
+ predictor.setFallback({ pShift: 0.8, pBackchannel: 0, pHold: 0 });
270
+ const policy = new VapInteractionPolicy({ predictor, fuseTranscript: semanticTranscriptFusion });
271
+ await policy.initialize();
272
+ policy.observe(audioFrame("fused"));
273
+ await flushInference();
274
+
275
+ policy.observe({
276
+ kind: "stt_partial",
277
+ contextId: "fused",
278
+ timestampMs: 1,
279
+ text: "I need to",
280
+ });
281
+ expect(policy.observe(audioFrame("fused"))).toEqual([{ kind: "keep_listening" }]);
282
+
283
+ policy.observe({
284
+ kind: "stt_final",
285
+ contextId: "fused",
286
+ timestampMs: 2,
287
+ text: "That is everything.",
288
+ });
289
+ expect(policy.observe(audioFrame("fused"))).toEqual([
290
+ { kind: "take_turn", confidence: 0.9 },
291
+ ]);
292
+
293
+ policy.reset("fused");
294
+ expect(policy.observe(audioFrame("fused"))).toEqual([{ kind: "keep_listening" }]);
295
+ });
296
+
297
+ it("observe audio_frame p99 stays <= 5ms on the sync path (test:vap-latency-bench)", async () => {
298
+ const predictor = new StubVapPredictor();
299
+ predictor.setFallback({
300
+ pShift: DEFAULT_VAP_THRESHOLDS.take + 0.2,
301
+ pBackchannel: 0,
302
+ pHold: 0,
303
+ });
304
+ const policy = new VapInteractionPolicy({ predictor });
305
+ await policy.initialize();
306
+
307
+ const frame = new Int16Array(320);
308
+ const samples = 1000;
309
+ const durations: number[] = [];
310
+ for (let i = 0; i < samples; i += 1) {
311
+ const start = performance.now();
312
+ policy.observe(audioFrame("bench", frame));
313
+ durations.push(performance.now() - start);
314
+ }
315
+
316
+ durations.sort((a, b) => a - b);
317
+ const p99 = durations[Math.floor(samples * 0.99) - 1] ?? durations[durations.length - 1] ?? 0;
318
+ expect(p99).toBeLessThanOrEqual(5);
319
+ });
320
+ });
321
+
322
+ describe("RollingFeatureBuffer", () => {
323
+ it("keeps chronological samples after wrap and returns stable snapshots", () => {
324
+ const buffer = new RollingFeatureBuffer(4);
325
+ buffer.append(new Int16Array([3_276, 6_553, 9_830]));
326
+ const first = buffer.snapshot();
327
+ buffer.append(new Int16Array([13_107, 16_384]));
328
+
329
+ expect([...first]).toEqual([
330
+ 3_276 / 32_768,
331
+ 6_553 / 32_768,
332
+ 9_830 / 32_768,
333
+ ]);
334
+ expect([...buffer.snapshot()]).toEqual([
335
+ 6_553 / 32_768,
336
+ 9_830 / 32_768,
337
+ 13_107 / 32_768,
338
+ 16_384 / 32_768,
339
+ ]);
340
+ });
341
+ });
package/models/.gitkeep DELETED
File without changes