@absolutejs/voice 0.0.22-beta.623 → 0.0.22-beta.625
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/core/types.d.ts +30 -0
- package/dist/index.js +49 -0
- package/dist/testing/index.js +48 -0
- package/package.json +1 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -810,6 +810,21 @@ export type VoicePluginConfig<TContext = unknown, TSession extends VoiceSessionR
|
|
|
810
810
|
}) => string | Promise<string>);
|
|
811
811
|
reason?: string;
|
|
812
812
|
};
|
|
813
|
+
/** Gentle re-engagement before a silent call is given up on. When the caller
|
|
814
|
+
* makes no progress (no committed turn / user partial) for `afterMs`, the
|
|
815
|
+
* assistant speaks `line` ("still there?") instead of ending, and the close
|
|
816
|
+
* deadline (stuckCallClose) is pushed out — up to `maxReprompts` times (default
|
|
817
|
+
* 1). After the budget is spent the normal close path runs. Caller speech
|
|
818
|
+
* resets the budget. Assistant speech does NOT, so this can only extend a
|
|
819
|
+
* bounded amount. `afterMs` should be < stuckCallClose.afterMs so the nudge
|
|
820
|
+
* fires first. */
|
|
821
|
+
idleReprompt?: {
|
|
822
|
+
afterMs: number;
|
|
823
|
+
line: string | ((input: {
|
|
824
|
+
session: TSession;
|
|
825
|
+
}) => string | Promise<string>);
|
|
826
|
+
maxReprompts?: number;
|
|
827
|
+
};
|
|
813
828
|
languageStrategy?: VoiceLanguageStrategy;
|
|
814
829
|
lexicon?: VoiceLexiconEntry[] | VoiceLexiconResolver<TContext>;
|
|
815
830
|
phraseHints?: VoicePhraseHint[] | VoicePhraseHintResolver<TContext>;
|
|
@@ -979,6 +994,21 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
979
994
|
}) => string | Promise<string>);
|
|
980
995
|
reason?: string;
|
|
981
996
|
};
|
|
997
|
+
/** Gentle re-engagement before a silent call is given up on. When the caller
|
|
998
|
+
* makes no progress (no committed turn / user partial) for `afterMs`, the
|
|
999
|
+
* assistant speaks `line` ("still there?") instead of ending, and the close
|
|
1000
|
+
* deadline (stuckCallClose) is pushed out — up to `maxReprompts` times (default
|
|
1001
|
+
* 1). After the budget is spent the normal close path runs. Caller speech
|
|
1002
|
+
* resets the budget. Assistant speech does NOT, so this can only extend a
|
|
1003
|
+
* bounded amount. `afterMs` should be < stuckCallClose.afterMs so the nudge
|
|
1004
|
+
* fires first. */
|
|
1005
|
+
idleReprompt?: {
|
|
1006
|
+
afterMs: number;
|
|
1007
|
+
line: string | ((input: {
|
|
1008
|
+
session: TSession;
|
|
1009
|
+
}) => string | Promise<string>);
|
|
1010
|
+
maxReprompts?: number;
|
|
1011
|
+
};
|
|
982
1012
|
stt?: STTAdapter;
|
|
983
1013
|
realtime?: RealtimeAdapter;
|
|
984
1014
|
realtimeInputFormat?: AudioFormat;
|
package/dist/index.js
CHANGED
|
@@ -4231,6 +4231,45 @@ var createVoiceSession = (options) => {
|
|
|
4231
4231
|
clearStuckCloseWatchdog();
|
|
4232
4232
|
stuckCloseWatchdog = setTimeout(fireStuckClose, stuckCloseAfterMs);
|
|
4233
4233
|
};
|
|
4234
|
+
const idleRepromptConfig = options.idleReprompt;
|
|
4235
|
+
const idleRepromptAfterMs = idleRepromptConfig && idleRepromptConfig.afterMs > 0 ? idleRepromptConfig.afterMs : undefined;
|
|
4236
|
+
const idleRepromptMax = Math.max(1, idleRepromptConfig?.maxReprompts ?? 1);
|
|
4237
|
+
let idleRepromptsUsed = 0;
|
|
4238
|
+
let idleRepromptWatchdog = null;
|
|
4239
|
+
const clearIdleRepromptWatchdog = () => {
|
|
4240
|
+
if (idleRepromptWatchdog) {
|
|
4241
|
+
clearTimeout(idleRepromptWatchdog);
|
|
4242
|
+
idleRepromptWatchdog = null;
|
|
4243
|
+
}
|
|
4244
|
+
};
|
|
4245
|
+
const kickIdleRepromptWatchdog = (resetBudget = false) => {
|
|
4246
|
+
if (idleRepromptAfterMs === undefined) {
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
if (resetBudget) {
|
|
4250
|
+
idleRepromptsUsed = 0;
|
|
4251
|
+
}
|
|
4252
|
+
clearIdleRepromptWatchdog();
|
|
4253
|
+
if (idleRepromptsUsed >= idleRepromptMax) {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
idleRepromptWatchdog = setTimeout(() => {
|
|
4257
|
+
idleRepromptWatchdog = null;
|
|
4258
|
+
if (idleRepromptConfig === undefined || idleRepromptsUsed >= idleRepromptMax) {
|
|
4259
|
+
return;
|
|
4260
|
+
}
|
|
4261
|
+
runSerial("idle-reprompt", async () => {
|
|
4262
|
+
const snapshot = await readSession();
|
|
4263
|
+
if (snapshot.status === "completed" || snapshot.status === "failed" || snapshot.call?.endedAt) {
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
idleRepromptsUsed += 1;
|
|
4267
|
+
await speakResolvedLine(idleRepromptConfig.line, snapshot);
|
|
4268
|
+
kickStuckCloseWatchdog();
|
|
4269
|
+
kickIdleRepromptWatchdog();
|
|
4270
|
+
});
|
|
4271
|
+
}, idleRepromptAfterMs);
|
|
4272
|
+
};
|
|
4234
4273
|
const recordingConfig = options.recording;
|
|
4235
4274
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
4236
4275
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -4728,6 +4767,7 @@ var createVoiceSession = (options) => {
|
|
|
4728
4767
|
});
|
|
4729
4768
|
clearCallSilenceWatchdog();
|
|
4730
4769
|
clearStuckCloseWatchdog();
|
|
4770
|
+
clearIdleRepromptWatchdog();
|
|
4731
4771
|
clearAmdEvaluationTimer();
|
|
4732
4772
|
await closeTTSSession("failed");
|
|
4733
4773
|
await closeAdapter("failed");
|
|
@@ -4838,6 +4878,7 @@ var createVoiceSession = (options) => {
|
|
|
4838
4878
|
});
|
|
4839
4879
|
clearCallSilenceWatchdog();
|
|
4840
4880
|
clearStuckCloseWatchdog();
|
|
4881
|
+
clearIdleRepromptWatchdog();
|
|
4841
4882
|
clearAmdEvaluationTimer();
|
|
4842
4883
|
await closeTTSSession("complete");
|
|
4843
4884
|
await closeAdapter("complete");
|
|
@@ -5317,6 +5358,7 @@ var createVoiceSession = (options) => {
|
|
|
5317
5358
|
const handlePartial = async (transcript) => {
|
|
5318
5359
|
if (transcript.text.trim()) {
|
|
5319
5360
|
kickStuckCloseWatchdog();
|
|
5361
|
+
kickIdleRepromptWatchdog(true);
|
|
5320
5362
|
}
|
|
5321
5363
|
if (activeTTSTurnId !== undefined) {
|
|
5322
5364
|
const triggeringText = transcript.text.trim();
|
|
@@ -5793,6 +5835,7 @@ var createVoiceSession = (options) => {
|
|
|
5793
5835
|
const completeTurn = async (session, turn) => {
|
|
5794
5836
|
console.error(`[voice] completeTurn ENTER session=${options.id} turn=${turn.id} textLen=${turn.text?.length ?? 0}`);
|
|
5795
5837
|
kickStuckCloseWatchdog();
|
|
5838
|
+
kickIdleRepromptWatchdog(true);
|
|
5796
5839
|
const liveOpsControl = await options.liveOps?.getControl(options.id);
|
|
5797
5840
|
if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
|
|
5798
5841
|
await appendTrace({
|
|
@@ -6495,6 +6538,7 @@ var createVoiceSession = (options) => {
|
|
|
6495
6538
|
warmTTSSession();
|
|
6496
6539
|
kickCallSilenceWatchdog();
|
|
6497
6540
|
kickStuckCloseWatchdog();
|
|
6541
|
+
kickIdleRepromptWatchdog(true);
|
|
6498
6542
|
startAmdEvaluationTimer();
|
|
6499
6543
|
if (options.greeting && session.turns.length === 0) {
|
|
6500
6544
|
await speakResolvedLine(options.greeting, session);
|
|
@@ -6641,6 +6685,7 @@ var createVoiceSession = (options) => {
|
|
|
6641
6685
|
clearSilenceTimer();
|
|
6642
6686
|
clearCallSilenceWatchdog();
|
|
6643
6687
|
clearStuckCloseWatchdog();
|
|
6688
|
+
clearIdleRepromptWatchdog();
|
|
6644
6689
|
clearAmdEvaluationTimer();
|
|
6645
6690
|
if (options.noiseSuppressor?.close) {
|
|
6646
6691
|
try {
|
|
@@ -25442,6 +25487,9 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
25442
25487
|
stt: options.stt,
|
|
25443
25488
|
sttFallback: resolveSTTFallbackConfig(options.sttFallback),
|
|
25444
25489
|
sttLifecycle: options.sttLifecycle ?? runtimePreset.sttLifecycle,
|
|
25490
|
+
sttRecoveryLine: options.sttRecoveryLine,
|
|
25491
|
+
stuckCallClose: options.stuckCallClose,
|
|
25492
|
+
idleReprompt: options.idleReprompt,
|
|
25445
25493
|
...options.semanticTurnDetector ? { semanticTurnDetector: options.semanticTurnDetector } : {},
|
|
25446
25494
|
...options.bargeInDetector ? { bargeInDetector: options.bargeInDetector } : {},
|
|
25447
25495
|
...options.fillerPhrases ? { fillerPhrases: options.fillerPhrases } : {},
|
|
@@ -39759,6 +39807,7 @@ var voice = (config) => {
|
|
|
39759
39807
|
resumeGreeting: config.resumeGreeting,
|
|
39760
39808
|
sttRecoveryLine: config.sttRecoveryLine,
|
|
39761
39809
|
stuckCallClose: config.stuckCallClose,
|
|
39810
|
+
idleReprompt: config.idleReprompt,
|
|
39762
39811
|
handoff: config.handoff,
|
|
39763
39812
|
languageStrategy: config.languageStrategy,
|
|
39764
39813
|
lexicon,
|
package/dist/testing/index.js
CHANGED
|
@@ -6551,6 +6551,45 @@ var createVoiceSession = (options) => {
|
|
|
6551
6551
|
clearStuckCloseWatchdog();
|
|
6552
6552
|
stuckCloseWatchdog = setTimeout(fireStuckClose, stuckCloseAfterMs);
|
|
6553
6553
|
};
|
|
6554
|
+
const idleRepromptConfig = options.idleReprompt;
|
|
6555
|
+
const idleRepromptAfterMs = idleRepromptConfig && idleRepromptConfig.afterMs > 0 ? idleRepromptConfig.afterMs : undefined;
|
|
6556
|
+
const idleRepromptMax = Math.max(1, idleRepromptConfig?.maxReprompts ?? 1);
|
|
6557
|
+
let idleRepromptsUsed = 0;
|
|
6558
|
+
let idleRepromptWatchdog = null;
|
|
6559
|
+
const clearIdleRepromptWatchdog = () => {
|
|
6560
|
+
if (idleRepromptWatchdog) {
|
|
6561
|
+
clearTimeout(idleRepromptWatchdog);
|
|
6562
|
+
idleRepromptWatchdog = null;
|
|
6563
|
+
}
|
|
6564
|
+
};
|
|
6565
|
+
const kickIdleRepromptWatchdog = (resetBudget = false) => {
|
|
6566
|
+
if (idleRepromptAfterMs === undefined) {
|
|
6567
|
+
return;
|
|
6568
|
+
}
|
|
6569
|
+
if (resetBudget) {
|
|
6570
|
+
idleRepromptsUsed = 0;
|
|
6571
|
+
}
|
|
6572
|
+
clearIdleRepromptWatchdog();
|
|
6573
|
+
if (idleRepromptsUsed >= idleRepromptMax) {
|
|
6574
|
+
return;
|
|
6575
|
+
}
|
|
6576
|
+
idleRepromptWatchdog = setTimeout(() => {
|
|
6577
|
+
idleRepromptWatchdog = null;
|
|
6578
|
+
if (idleRepromptConfig === undefined || idleRepromptsUsed >= idleRepromptMax) {
|
|
6579
|
+
return;
|
|
6580
|
+
}
|
|
6581
|
+
runSerial("idle-reprompt", async () => {
|
|
6582
|
+
const snapshot = await readSession();
|
|
6583
|
+
if (snapshot.status === "completed" || snapshot.status === "failed" || snapshot.call?.endedAt) {
|
|
6584
|
+
return;
|
|
6585
|
+
}
|
|
6586
|
+
idleRepromptsUsed += 1;
|
|
6587
|
+
await speakResolvedLine(idleRepromptConfig.line, snapshot);
|
|
6588
|
+
kickStuckCloseWatchdog();
|
|
6589
|
+
kickIdleRepromptWatchdog();
|
|
6590
|
+
});
|
|
6591
|
+
}, idleRepromptAfterMs);
|
|
6592
|
+
};
|
|
6554
6593
|
const recordingConfig = options.recording;
|
|
6555
6594
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
6556
6595
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -7048,6 +7087,7 @@ var createVoiceSession = (options) => {
|
|
|
7048
7087
|
});
|
|
7049
7088
|
clearCallSilenceWatchdog();
|
|
7050
7089
|
clearStuckCloseWatchdog();
|
|
7090
|
+
clearIdleRepromptWatchdog();
|
|
7051
7091
|
clearAmdEvaluationTimer();
|
|
7052
7092
|
await closeTTSSession("failed");
|
|
7053
7093
|
await closeAdapter("failed");
|
|
@@ -7158,6 +7198,7 @@ var createVoiceSession = (options) => {
|
|
|
7158
7198
|
});
|
|
7159
7199
|
clearCallSilenceWatchdog();
|
|
7160
7200
|
clearStuckCloseWatchdog();
|
|
7201
|
+
clearIdleRepromptWatchdog();
|
|
7161
7202
|
clearAmdEvaluationTimer();
|
|
7162
7203
|
await closeTTSSession("complete");
|
|
7163
7204
|
await closeAdapter("complete");
|
|
@@ -7637,6 +7678,7 @@ var createVoiceSession = (options) => {
|
|
|
7637
7678
|
const handlePartial = async (transcript) => {
|
|
7638
7679
|
if (transcript.text.trim()) {
|
|
7639
7680
|
kickStuckCloseWatchdog();
|
|
7681
|
+
kickIdleRepromptWatchdog(true);
|
|
7640
7682
|
}
|
|
7641
7683
|
if (activeTTSTurnId !== undefined) {
|
|
7642
7684
|
const triggeringText = transcript.text.trim();
|
|
@@ -8113,6 +8155,7 @@ var createVoiceSession = (options) => {
|
|
|
8113
8155
|
const completeTurn = async (session, turn) => {
|
|
8114
8156
|
console.error(`[voice] completeTurn ENTER session=${options.id} turn=${turn.id} textLen=${turn.text?.length ?? 0}`);
|
|
8115
8157
|
kickStuckCloseWatchdog();
|
|
8158
|
+
kickIdleRepromptWatchdog(true);
|
|
8116
8159
|
const liveOpsControl = await options.liveOps?.getControl(options.id);
|
|
8117
8160
|
if (liveOpsControl?.assistantPaused || liveOpsControl?.operatorTakeover) {
|
|
8118
8161
|
await appendTrace({
|
|
@@ -8815,6 +8858,7 @@ var createVoiceSession = (options) => {
|
|
|
8815
8858
|
warmTTSSession();
|
|
8816
8859
|
kickCallSilenceWatchdog();
|
|
8817
8860
|
kickStuckCloseWatchdog();
|
|
8861
|
+
kickIdleRepromptWatchdog(true);
|
|
8818
8862
|
startAmdEvaluationTimer();
|
|
8819
8863
|
if (options.greeting && session.turns.length === 0) {
|
|
8820
8864
|
await speakResolvedLine(options.greeting, session);
|
|
@@ -8961,6 +9005,7 @@ var createVoiceSession = (options) => {
|
|
|
8961
9005
|
clearSilenceTimer();
|
|
8962
9006
|
clearCallSilenceWatchdog();
|
|
8963
9007
|
clearStuckCloseWatchdog();
|
|
9008
|
+
clearIdleRepromptWatchdog();
|
|
8964
9009
|
clearAmdEvaluationTimer();
|
|
8965
9010
|
if (options.noiseSuppressor?.close) {
|
|
8966
9011
|
try {
|
|
@@ -14408,6 +14453,9 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
14408
14453
|
stt: options.stt,
|
|
14409
14454
|
sttFallback: resolveSTTFallbackConfig(options.sttFallback),
|
|
14410
14455
|
sttLifecycle: options.sttLifecycle ?? runtimePreset.sttLifecycle,
|
|
14456
|
+
sttRecoveryLine: options.sttRecoveryLine,
|
|
14457
|
+
stuckCallClose: options.stuckCallClose,
|
|
14458
|
+
idleReprompt: options.idleReprompt,
|
|
14411
14459
|
...options.semanticTurnDetector ? { semanticTurnDetector: options.semanticTurnDetector } : {},
|
|
14412
14460
|
...options.bargeInDetector ? { bargeInDetector: options.bargeInDetector } : {},
|
|
14413
14461
|
...options.fillerPhrases ? { fillerPhrases: options.fillerPhrases } : {},
|