botium-connector-voip 0.0.30 → 0.0.31
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.
|
@@ -78,6 +78,7 @@ const Capabilities = {
|
|
|
78
78
|
VOIP_STT_MESSAGE_HANDLING: 'VOIP_STT_MESSAGE_HANDLING',
|
|
79
79
|
VOIP_STT_MESSAGE_HANDLING_TIMEOUT: 'VOIP_STT_MESSAGE_HANDLING_TIMEOUT',
|
|
80
80
|
VOIP_STT_MESSAGE_HANDLING_TIMEOUT_SUBSEQUENT: 'VOIP_STT_MESSAGE_HANDLING_TIMEOUT_SUBSEQUENT',
|
|
81
|
+
VOIP_STT_MESSAGE_HANDLING_LATENCY_GRACE_MS: 'VOIP_STT_MESSAGE_HANDLING_LATENCY_GRACE_MS',
|
|
81
82
|
VOIP_JOIN_SILENCE_DURATION_BY_SUBSTRING: 'VOIP_JOIN_SILENCE_DURATION_BY_SUBSTRING',
|
|
82
83
|
VOIP_STT_DICTIONARY_REPLACEMENTS: 'VOIP_STT_DICTIONARY_REPLACEMENTS',
|
|
83
84
|
VOIP_STT_MESSAGE_HANDLING_DELIMITER: 'VOIP_STT_MESSAGE_HANDLING_DELIMITER',
|
|
@@ -137,6 +138,10 @@ const Defaults = {
|
|
|
137
138
|
VOIP_TTS_PREFETCH_ENABLE: true,
|
|
138
139
|
VOIP_STT_MESSAGE_HANDLING: 'ORIGINAL',
|
|
139
140
|
VOIP_STT_MESSAGE_HANDLING_TIMEOUT: 2500,
|
|
141
|
+
// Extra wall-clock grace added to the JOIN/PSST flush window when the last buffered final was
|
|
142
|
+
// cut mid-utterance (no Azure-detected end-of-speech silence). Absorbs STT delivery latency so a
|
|
143
|
+
// paused-then-resumed prompt is joined instead of split. Set to 0 to disable. See _getPsstLatencyGraceMs.
|
|
144
|
+
VOIP_STT_MESSAGE_HANDLING_LATENCY_GRACE_MS: 1500,
|
|
140
145
|
VOIP_STT_MESSAGE_HANDLING_DELIMITER: '. ',
|
|
141
146
|
VOIP_STT_MESSAGE_HANDLING_PUNCTUATION: '.!?',
|
|
142
147
|
VOIP_WEBSOCKET_CONNECT_TIMEOUT: 4000,
|
|
@@ -383,6 +388,15 @@ class BotiumConnectorVoip {
|
|
|
383
388
|
const isJoinMethod = sttHandling === 'JOIN' || sttHandling === 'PSST' || sttHandling === 'CONCAT' || this._hasJoinLogicHookOrRule(this.convoStep);
|
|
384
389
|
if (!isJoinMethod) return;
|
|
385
390
|
const joinTimeoutMs = this._getEffectiveJoinTimeoutMs(this.convoStep, this.botMsgs);
|
|
391
|
+
// Variant-3 latency grace: when the last buffered final was cut mid-utterance (no
|
|
392
|
+
// Azure-detected end-of-speech silence) a continuation is plausible but its STT delivery
|
|
393
|
+
// lags behind the audio by ~1s. Extend the wall-clock flush window by the grace so the
|
|
394
|
+
// resumed segment's partial/final (or a worker speechResumed event) can re-arm this timer
|
|
395
|
+
// and be joined, instead of flushing the first fragment on its own. A natural-ended final
|
|
396
|
+
// keeps the base window and flushes fast.
|
|
397
|
+
const graceMs = this._getPsstLatencyGraceMs();
|
|
398
|
+
const graceApplied = graceMs > 0 && lodash__default["default"].isFinite(joinTimeoutMs) && joinTimeoutMs > 0 && !this._isLastFinalNaturalEnd();
|
|
399
|
+
const effectiveWindowMs = graceApplied ? joinTimeoutMs + graceMs : joinTimeoutMs || 0;
|
|
386
400
|
if (this.silenceTimeout) {
|
|
387
401
|
clearTimeout(this.silenceTimeout);
|
|
388
402
|
this.silenceTimeout = null;
|
|
@@ -391,21 +405,24 @@ class BotiumConnectorVoip {
|
|
|
391
405
|
const armedAt = Date.now();
|
|
392
406
|
this._markReplyTrace({
|
|
393
407
|
psstTimerArmedAtMs: armedAt,
|
|
394
|
-
psstScheduledMs:
|
|
408
|
+
psstScheduledMs: effectiveWindowMs || 0
|
|
395
409
|
});
|
|
396
410
|
_info('psst_timer_armed', {
|
|
397
411
|
sessionId: this.sessionId,
|
|
398
|
-
joinTimeoutMs:
|
|
412
|
+
joinTimeoutMs: effectiveWindowMs || 0,
|
|
413
|
+
baseTimeoutMs: joinTimeoutMs || 0,
|
|
414
|
+
graceMs: graceApplied ? graceMs : 0,
|
|
415
|
+
graceApplied,
|
|
399
416
|
bufferedChunks: bufferedAtArm,
|
|
400
417
|
stopCalled: !!this.stopCalled
|
|
401
418
|
});
|
|
402
|
-
// Emit the authoritative
|
|
419
|
+
// Emit the authoritative flush window so downstream consumers (e.g.
|
|
403
420
|
// SpeculationBuffer) can size their quiet threshold relative to it.
|
|
404
|
-
if (this.eventEmitter && lodash__default["default"].isFinite(
|
|
421
|
+
if (this.eventEmitter && lodash__default["default"].isFinite(effectiveWindowMs) && effectiveWindowMs > 0) {
|
|
405
422
|
try {
|
|
406
423
|
this.eventEmitter.emit('voip.psstTimerArmed', {
|
|
407
424
|
sessionId: this.sessionId,
|
|
408
|
-
joinTimeoutMs,
|
|
425
|
+
joinTimeoutMs: effectiveWindowMs,
|
|
409
426
|
bufferedChunks: bufferedAtArm,
|
|
410
427
|
armedAt
|
|
411
428
|
});
|
|
@@ -425,10 +442,11 @@ class BotiumConnectorVoip {
|
|
|
425
442
|
sessionId: this.sessionId,
|
|
426
443
|
bufferedChunks: this.botMsgs.length,
|
|
427
444
|
actualDelayMs: fireDelay,
|
|
428
|
-
scheduledDelayMs:
|
|
445
|
+
scheduledDelayMs: effectiveWindowMs || 0,
|
|
446
|
+
graceApplied,
|
|
429
447
|
outcome: 'emit'
|
|
430
448
|
});
|
|
431
|
-
debug$3('Silence Duration Timeout (JOIN/PSST):',
|
|
449
|
+
debug$3('Silence Duration Timeout (JOIN/PSST):', effectiveWindowMs, 'ms');
|
|
432
450
|
sendBotMsg(joinBotMsg(this.botMsgs, this.joinLastPrevMsg));
|
|
433
451
|
this.firstMsg = false;
|
|
434
452
|
this.joinLastPrevMsg = this.botMsgs[this.botMsgs.length - 1];
|
|
@@ -441,11 +459,11 @@ class BotiumConnectorVoip {
|
|
|
441
459
|
sessionId: this.sessionId,
|
|
442
460
|
bufferedChunks: 0,
|
|
443
461
|
actualDelayMs: fireDelay,
|
|
444
|
-
scheduledDelayMs:
|
|
462
|
+
scheduledDelayMs: effectiveWindowMs || 0,
|
|
445
463
|
outcome: 'noop_empty_buffer'
|
|
446
464
|
});
|
|
447
465
|
}
|
|
448
|
-
},
|
|
466
|
+
}, effectiveWindowMs || 0);
|
|
449
467
|
};
|
|
450
468
|
|
|
451
469
|
// Flush buffered STT chunks on teardown so a late final is not lost when
|
|
@@ -1076,6 +1094,36 @@ class BotiumConnectorVoip {
|
|
|
1076
1094
|
}
|
|
1077
1095
|
}
|
|
1078
1096
|
}
|
|
1097
|
+
if (parsedData && parsedData.type === 'speech' && parsedData.event === 'onSpeechResumed') {
|
|
1098
|
+
// Positive VAD signal from the worker: the bot started speaking again after a pause.
|
|
1099
|
+
// It arrives ~1s before the resumed segment's first STT partial, so when we are
|
|
1100
|
+
// buffering finals (PSST/JOIN) we re-arm the flush timer now — keeping the buffered
|
|
1101
|
+
// fragment open to be joined instead of flushed on its own. Bounded by the same
|
|
1102
|
+
// extension budget as partial-driven re-arms to prevent infinite stranding.
|
|
1103
|
+
_info('speech_resumed', {
|
|
1104
|
+
sessionId: this.sessionId
|
|
1105
|
+
});
|
|
1106
|
+
const sttHandling = this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING];
|
|
1107
|
+
const isJoinMethod = sttHandling === 'JOIN' || sttHandling === 'PSST' || sttHandling === 'CONCAT' || this._hasJoinLogicHookOrRule(this.convoStep);
|
|
1108
|
+
if (isJoinMethod && this.botMsgs && this.botMsgs.length > 0) {
|
|
1109
|
+
const MAX_EXTENSION_MS = 60000;
|
|
1110
|
+
const now = Date.now();
|
|
1111
|
+
const withinCap = this.psstFirstRearmAt == null || now - this.psstFirstRearmAt < MAX_EXTENSION_MS;
|
|
1112
|
+
if (withinCap) {
|
|
1113
|
+
if (this.psstFirstRearmAt == null) this.psstFirstRearmAt = now;
|
|
1114
|
+
this.psstRearmCount++;
|
|
1115
|
+
_info('psst_timer_extended', {
|
|
1116
|
+
sessionId: this.sessionId,
|
|
1117
|
+
rearmCount: this.psstRearmCount,
|
|
1118
|
+
msSinceFirstRearm: now - this.psstFirstRearmAt,
|
|
1119
|
+
bufferedChunks: this.botMsgs.length,
|
|
1120
|
+
reason: 'speech_resumed',
|
|
1121
|
+
silenceDurationMs: lodash__default["default"].get(parsedData, 'data.silenceDurationMs', null)
|
|
1122
|
+
});
|
|
1123
|
+
armJoinSilenceTimer();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1079
1127
|
if (parsedData && parsedData.type === 'fullRecord') {
|
|
1080
1128
|
// Non-chunked full record
|
|
1081
1129
|
const base64 = _extractFullRecordBase64(parsedData);
|
|
@@ -2458,6 +2506,21 @@ class BotiumConnectorVoip {
|
|
|
2458
2506
|
if (!lodash__default["default"].isFinite(parsed) || parsed <= 0) return null;
|
|
2459
2507
|
return isPsst ? Math.max(0, parsed - 500) : parsed;
|
|
2460
2508
|
}
|
|
2509
|
+
|
|
2510
|
+
// Extra grace (ms) added to the JOIN/PSST flush window when the last buffered final was cut
|
|
2511
|
+
// mid-utterance. Returns 0 when unset/invalid (feature off). See _isLastFinalNaturalEnd.
|
|
2512
|
+
_getPsstLatencyGraceMs() {
|
|
2513
|
+
const parsed = parseInt(this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING_LATENCY_GRACE_MS], 10);
|
|
2514
|
+
if (!lodash__default["default"].isFinite(parsed) || parsed <= 0) return 0;
|
|
2515
|
+
return parsed;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// A final that carries a finite silenceStartedSec ended on Azure-detected end-of-speech silence
|
|
2519
|
+
// (bot genuinely stopped) => flush at the base window, no grace. A final without it was cut
|
|
2520
|
+
// mid-utterance (forced/segmented) => a continuation is plausible => apply the latency grace.
|
|
2521
|
+
_isLastFinalNaturalEnd() {
|
|
2522
|
+
return lodash__default["default"].isFinite(lodash__default["default"].get(this.prevData, 'data.silenceStartedSec'));
|
|
2523
|
+
}
|
|
2461
2524
|
_getEffectiveJoinTimeoutMs(convoStep, botMsgs) {
|
|
2462
2525
|
const sttHandling = this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING];
|
|
2463
2526
|
const isPsst = sttHandling === 'PSST';
|