@absolutejs/voice 0.0.22-beta.553 → 0.0.22-beta.554
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 +7 -0
- package/dist/index.js +48 -0
- package/dist/telephony/twilio.d.ts +7 -0
- package/dist/testing/index.js +48 -0
- package/package.json +1 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -916,6 +916,13 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
916
916
|
fillerPhrases?: ReadonlyArray<string>;
|
|
917
917
|
/** Milliseconds after turn-commit before the filler fires. Default 250ms — short enough to feel instant, long enough to skip if the LLM is very fast. */
|
|
918
918
|
fillerDelayMs?: number;
|
|
919
|
+
/**
|
|
920
|
+
* Default spoken ack if the model returns ONLY tool calls (no text) and the
|
|
921
|
+
* turn isn't ending. Without this, the caller hears total silence after
|
|
922
|
+
* their turn and assumes the line dropped. Default is "Sorry, one moment."
|
|
923
|
+
* Set to "" to opt out entirely.
|
|
924
|
+
*/
|
|
925
|
+
defaultSilentTurnAck?: string;
|
|
919
926
|
assistantMode?: import("./assistantMode").VoiceAssistantMode;
|
|
920
927
|
modalities?: ReadonlyArray<"audio" | "text">;
|
|
921
928
|
prosody?: VoiceTTSProsody;
|
package/dist/index.js
CHANGED
|
@@ -5464,6 +5464,53 @@ var createVoiceSession = (options) => {
|
|
|
5464
5464
|
});
|
|
5465
5465
|
}
|
|
5466
5466
|
}
|
|
5467
|
+
const audioWasSent = Boolean(streamResult?.streamed) || Boolean(output?.assistantText?.trim());
|
|
5468
|
+
const turnIsEnding = Boolean(output?.complete) || Boolean(output?.transfer) || Boolean(output?.escalate) || Boolean(output?.voicemail) || Boolean(output?.noAnswer);
|
|
5469
|
+
if (!audioWasSent && !turnIsEnding) {
|
|
5470
|
+
const fallback = typeof options.defaultSilentTurnAck === "string" ? options.defaultSilentTurnAck : "Sorry, one moment.";
|
|
5471
|
+
if (fallback.trim() && options.tts) {
|
|
5472
|
+
try {
|
|
5473
|
+
const activeTTSSession = await ensureTTSSession();
|
|
5474
|
+
if (activeTTSSession) {
|
|
5475
|
+
fillerToken += 1;
|
|
5476
|
+
if (fillerTimer) {
|
|
5477
|
+
clearTimeout(fillerTimer);
|
|
5478
|
+
fillerTimer = null;
|
|
5479
|
+
}
|
|
5480
|
+
if (fillerActive) {
|
|
5481
|
+
await cancelActiveTTS("filler-superseded").catch(() => {});
|
|
5482
|
+
fillerActive = false;
|
|
5483
|
+
}
|
|
5484
|
+
activeTTSTurnId = turn.id;
|
|
5485
|
+
await activeTTSSession.send(fallback);
|
|
5486
|
+
await appendTrace({
|
|
5487
|
+
payload: {
|
|
5488
|
+
assistantMode: resolveVoiceAssistantMode(options),
|
|
5489
|
+
fallback: true,
|
|
5490
|
+
realtimeConfigured: Boolean(options.realtime),
|
|
5491
|
+
reason: "model-returned-no-text",
|
|
5492
|
+
text: fallback,
|
|
5493
|
+
ttsConfigured: Boolean(options.tts)
|
|
5494
|
+
},
|
|
5495
|
+
session,
|
|
5496
|
+
turnId: turn.id,
|
|
5497
|
+
type: "turn.assistant"
|
|
5498
|
+
});
|
|
5499
|
+
if (options.costAccountant) {
|
|
5500
|
+
options.costAccountant.recordTTS({
|
|
5501
|
+
characters: fallback.length
|
|
5502
|
+
});
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
} catch (error) {
|
|
5506
|
+
logger.warn("voice default-silent-turn-ack fallback send failed", {
|
|
5507
|
+
error: toError(error).message,
|
|
5508
|
+
sessionId: options.id,
|
|
5509
|
+
turnId: turn.id
|
|
5510
|
+
});
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
}
|
|
5467
5514
|
if (output?.result !== undefined) {
|
|
5468
5515
|
await writeSession((currentSession) => {
|
|
5469
5516
|
setTurnResult(currentSession, turn.id, {
|
|
@@ -24608,6 +24655,7 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
24608
24655
|
...options.semanticTurnDetector ? { semanticTurnDetector: options.semanticTurnDetector } : {},
|
|
24609
24656
|
...options.fillerPhrases ? { fillerPhrases: options.fillerPhrases } : {},
|
|
24610
24657
|
...options.fillerDelayMs !== undefined ? { fillerDelayMs: options.fillerDelayMs } : {},
|
|
24658
|
+
...options.defaultSilentTurnAck !== undefined ? { defaultSilentTurnAck: options.defaultSilentTurnAck } : {},
|
|
24611
24659
|
trace: options.trace,
|
|
24612
24660
|
tts: options.tts,
|
|
24613
24661
|
turnDetection
|
|
@@ -132,6 +132,13 @@ export type TwilioMediaStreamBridgeOptions<TContext = unknown, TSession extends
|
|
|
132
132
|
fillerPhrases?: ReadonlyArray<string>;
|
|
133
133
|
/** Milliseconds after turn-commit before the filler fires. Default 250ms. */
|
|
134
134
|
fillerDelayMs?: number;
|
|
135
|
+
/**
|
|
136
|
+
* Default spoken ack if the model returns ONLY tool calls (no text) and
|
|
137
|
+
* the turn isn't ending. Without this, the caller hears silence and
|
|
138
|
+
* assumes the line dropped. Default "Sorry, one moment." — set to ""
|
|
139
|
+
* to opt out. See CreateVoiceSessionOptions for full semantics.
|
|
140
|
+
*/
|
|
141
|
+
defaultSilentTurnAck?: string;
|
|
135
142
|
};
|
|
136
143
|
export type TwilioMediaStreamBridge = {
|
|
137
144
|
close: (reason?: string) => Promise<void>;
|
package/dist/testing/index.js
CHANGED
|
@@ -7281,6 +7281,53 @@ var createVoiceSession = (options) => {
|
|
|
7281
7281
|
});
|
|
7282
7282
|
}
|
|
7283
7283
|
}
|
|
7284
|
+
const audioWasSent = Boolean(streamResult?.streamed) || Boolean(output?.assistantText?.trim());
|
|
7285
|
+
const turnIsEnding = Boolean(output?.complete) || Boolean(output?.transfer) || Boolean(output?.escalate) || Boolean(output?.voicemail) || Boolean(output?.noAnswer);
|
|
7286
|
+
if (!audioWasSent && !turnIsEnding) {
|
|
7287
|
+
const fallback = typeof options.defaultSilentTurnAck === "string" ? options.defaultSilentTurnAck : "Sorry, one moment.";
|
|
7288
|
+
if (fallback.trim() && options.tts) {
|
|
7289
|
+
try {
|
|
7290
|
+
const activeTTSSession = await ensureTTSSession();
|
|
7291
|
+
if (activeTTSSession) {
|
|
7292
|
+
fillerToken += 1;
|
|
7293
|
+
if (fillerTimer) {
|
|
7294
|
+
clearTimeout(fillerTimer);
|
|
7295
|
+
fillerTimer = null;
|
|
7296
|
+
}
|
|
7297
|
+
if (fillerActive) {
|
|
7298
|
+
await cancelActiveTTS("filler-superseded").catch(() => {});
|
|
7299
|
+
fillerActive = false;
|
|
7300
|
+
}
|
|
7301
|
+
activeTTSTurnId = turn.id;
|
|
7302
|
+
await activeTTSSession.send(fallback);
|
|
7303
|
+
await appendTrace({
|
|
7304
|
+
payload: {
|
|
7305
|
+
assistantMode: resolveVoiceAssistantMode(options),
|
|
7306
|
+
fallback: true,
|
|
7307
|
+
realtimeConfigured: Boolean(options.realtime),
|
|
7308
|
+
reason: "model-returned-no-text",
|
|
7309
|
+
text: fallback,
|
|
7310
|
+
ttsConfigured: Boolean(options.tts)
|
|
7311
|
+
},
|
|
7312
|
+
session,
|
|
7313
|
+
turnId: turn.id,
|
|
7314
|
+
type: "turn.assistant"
|
|
7315
|
+
});
|
|
7316
|
+
if (options.costAccountant) {
|
|
7317
|
+
options.costAccountant.recordTTS({
|
|
7318
|
+
characters: fallback.length
|
|
7319
|
+
});
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
7322
|
+
} catch (error) {
|
|
7323
|
+
logger.warn("voice default-silent-turn-ack fallback send failed", {
|
|
7324
|
+
error: toError(error).message,
|
|
7325
|
+
sessionId: options.id,
|
|
7326
|
+
turnId: turn.id
|
|
7327
|
+
});
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7284
7331
|
if (output?.result !== undefined) {
|
|
7285
7332
|
await writeSession((currentSession) => {
|
|
7286
7333
|
setTurnResult(currentSession, turn.id, {
|
|
@@ -13144,6 +13191,7 @@ var createTwilioMediaStreamBridge = (socket, options) => {
|
|
|
13144
13191
|
...options.semanticTurnDetector ? { semanticTurnDetector: options.semanticTurnDetector } : {},
|
|
13145
13192
|
...options.fillerPhrases ? { fillerPhrases: options.fillerPhrases } : {},
|
|
13146
13193
|
...options.fillerDelayMs !== undefined ? { fillerDelayMs: options.fillerDelayMs } : {},
|
|
13194
|
+
...options.defaultSilentTurnAck !== undefined ? { defaultSilentTurnAck: options.defaultSilentTurnAck } : {},
|
|
13147
13195
|
trace: options.trace,
|
|
13148
13196
|
tts: options.tts,
|
|
13149
13197
|
turnDetection
|