@absolutejs/voice 0.0.22-beta.477 → 0.0.22-beta.479
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/agentTools.d.ts +1 -0
- package/dist/amdDetector.d.ts +25 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +244 -38
- package/dist/outcomeRecipes.d.ts +1 -1
- package/dist/s3Store.d.ts +15 -0
- package/dist/testing/index.js +141 -36
- package/dist/types.d.ts +5 -1
- package/package.json +1 -1
package/dist/agentTools.d.ts
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Transcript, VoiceSessionHandle, VoiceSessionRecord } from "./types";
|
|
2
|
+
export type VoiceAMDDetectorInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
3
|
+
api: VoiceSessionHandle<TContext, TSession, TResult>;
|
|
4
|
+
audioLevel: number | undefined;
|
|
5
|
+
elapsedSinceFirstAudioMs: number;
|
|
6
|
+
elapsedSinceLastTurnCommitMs: number;
|
|
7
|
+
partialTranscript: string;
|
|
8
|
+
session: TSession;
|
|
9
|
+
transcripts: Transcript[];
|
|
10
|
+
};
|
|
11
|
+
export type VoiceAMDVerdict = {
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
reason?: string;
|
|
14
|
+
};
|
|
15
|
+
export type VoiceAMDDetector<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
16
|
+
evaluate: (input: VoiceAMDDetectorInput<TContext, TSession, TResult>) => Promise<VoiceAMDVerdict | undefined> | VoiceAMDVerdict | undefined;
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
};
|
|
19
|
+
export type MonologueAMDDetectorOptions = {
|
|
20
|
+
intervalMs?: number;
|
|
21
|
+
minMonologueMs?: number;
|
|
22
|
+
reason?: string;
|
|
23
|
+
requireFirstAudio?: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare const createMonologueAMDDetector: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options?: MonologueAMDDetectorOptions) => VoiceAMDDetector<TContext, TSession, TResult>;
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,8 @@ export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, crea
|
|
|
71
71
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
|
|
72
72
|
export { createAIVoiceModel } from "./aiVoiceModel";
|
|
73
73
|
export type { CreateAIVoiceModelOptions } from "./aiVoiceModel";
|
|
74
|
+
export { createMonologueAMDDetector } from "./amdDetector";
|
|
75
|
+
export type { MonologueAMDDetectorOptions, VoiceAMDDetector, VoiceAMDDetectorInput, VoiceAMDVerdict, } from "./amdDetector";
|
|
74
76
|
export { createVoiceRAGTool } from "./ragTool";
|
|
75
77
|
export type { VoiceRAGCollectionLike, VoiceRAGQueryResult, VoiceRAGSearchInput, VoiceRAGToolArgs, VoiceRAGToolOptions, VoiceRAGToolResult, } from "./ragTool";
|
|
76
78
|
export { createVoiceApiRequestTool, createVoiceDTMFTool, createVoiceEndCallTool, createVoiceTransferCallTool, createVoiceVoicemailDetectionTool, } from "./agentTools";
|
|
@@ -120,7 +122,7 @@ export { buildVoiceTraceDeliveryReport, createVoiceTraceDeliveryHTMLHandler, cre
|
|
|
120
122
|
export { createVoiceTraceTimelineRoutes, renderVoiceTraceTimelineHTML, renderVoiceTraceTimelineSessionHTML, summarizeVoiceTraceTimeline, } from "./traceTimeline";
|
|
121
123
|
export { createVoiceSQLiteAuditEventStore, createVoiceSQLiteAuditSinkDeliveryStore, createVoiceSQLiteCampaignStore, createVoiceSQLiteExternalObjectMapStore, createVoiceSQLiteIntegrationEventStore, createVoiceSQLiteReviewStore, createVoiceSQLiteRuntimeStorage, createVoiceSQLiteSessionStore, createVoiceSQLiteTaskStore, createVoiceSQLiteTelephonyWebhookIdempotencyStore, createVoiceSQLiteTraceSinkDeliveryStore, createVoiceSQLiteTraceEventStore, } from "./sqliteStore";
|
|
122
124
|
export { createVoicePostgresAuditEventStore, createVoicePostgresAuditSinkDeliveryStore, createVoicePostgresCampaignStore, createVoicePostgresExternalObjectMapStore, createVoicePostgresIntegrationEventStore, createVoicePostgresReviewStore, createVoicePostgresRuntimeStorage, createVoicePostgresSessionStore, createVoicePostgresTaskStore, createVoicePostgresTelephonyWebhookIdempotencyStore, createVoicePostgresTraceSinkDeliveryStore, createVoicePostgresTraceEventStore, } from "./postgresStore";
|
|
123
|
-
export { createVoiceS3ReviewStore } from "./s3Store";
|
|
125
|
+
export { createVoiceS3RecordingStore, createVoiceS3ReviewStore } from "./s3Store";
|
|
124
126
|
export { createVoiceMemoryStore } from "./memoryStore";
|
|
125
127
|
export { createVoiceCRMActivitySink, createVoiceHelpdeskTicketSink, createVoiceIntegrationHTTPSink, createVoiceHubSpotTaskSink, createVoiceHubSpotTaskSyncSinks, createVoiceHubSpotTaskUpdateSink, createVoiceLinearIssueSink, createVoiceLinearIssueSyncSinks, createVoiceLinearIssueUpdateSink, createVoiceZendeskTicketSink, createVoiceZendeskTicketSyncSinks, createVoiceZendeskTicketUpdateSink, deliverVoiceIntegrationEventToSinks, } from "./opsSinks";
|
|
126
128
|
export { createVoiceOpsWebhookEnvelope, createVoiceOpsWebhookReceiverRoutes, createVoiceOpsWebhookSink, verifyVoiceOpsWebhookSignature, } from "./opsWebhook";
|
package/dist/index.js
CHANGED
|
@@ -3682,6 +3682,90 @@ var createVoiceSession = (options) => {
|
|
|
3682
3682
|
const currentTurnAudio = [];
|
|
3683
3683
|
let fallbackAttemptsForCurrentTurn = 0;
|
|
3684
3684
|
let fallbackReplayAudioMsForCurrentTurn = 0;
|
|
3685
|
+
const amdDetector = options.amd;
|
|
3686
|
+
let amdEvaluationTimer = null;
|
|
3687
|
+
let amdFired = false;
|
|
3688
|
+
let amdFirstAudioAt;
|
|
3689
|
+
let amdLastTurnCommitAt;
|
|
3690
|
+
let amdLastAudioLevel;
|
|
3691
|
+
const clearAmdEvaluationTimer = () => {
|
|
3692
|
+
if (amdEvaluationTimer) {
|
|
3693
|
+
clearInterval(amdEvaluationTimer);
|
|
3694
|
+
amdEvaluationTimer = null;
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
const evaluateAmd = async () => {
|
|
3698
|
+
if (!amdDetector || amdFired) {
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
let snapshot;
|
|
3702
|
+
try {
|
|
3703
|
+
snapshot = await readSession();
|
|
3704
|
+
} catch {
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
const now = Date.now();
|
|
3708
|
+
const verdict = await Promise.resolve(amdDetector.evaluate({
|
|
3709
|
+
api,
|
|
3710
|
+
audioLevel: amdLastAudioLevel,
|
|
3711
|
+
elapsedSinceFirstAudioMs: amdFirstAudioAt === undefined ? 0 : now - amdFirstAudioAt,
|
|
3712
|
+
elapsedSinceLastTurnCommitMs: amdLastTurnCommitAt === undefined ? 0 : now - amdLastTurnCommitAt,
|
|
3713
|
+
partialTranscript: snapshot.currentTurn.partialText,
|
|
3714
|
+
session: snapshot,
|
|
3715
|
+
transcripts: [
|
|
3716
|
+
...snapshot.transcripts,
|
|
3717
|
+
...snapshot.currentTurn.transcripts
|
|
3718
|
+
]
|
|
3719
|
+
}));
|
|
3720
|
+
if (!verdict || amdFired) {
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
amdFired = true;
|
|
3724
|
+
clearAmdEvaluationTimer();
|
|
3725
|
+
try {
|
|
3726
|
+
await api.markVoicemail({
|
|
3727
|
+
metadata: verdict.metadata
|
|
3728
|
+
});
|
|
3729
|
+
} catch (error) {
|
|
3730
|
+
logger.warn("voice amd markVoicemail failed", {
|
|
3731
|
+
error: toError(error).message,
|
|
3732
|
+
sessionId: options.id
|
|
3733
|
+
});
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
const startAmdEvaluationTimer = () => {
|
|
3737
|
+
if (!amdDetector || amdEvaluationTimer || amdFired) {
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
const intervalMs = amdDetector.intervalMs ?? 1000;
|
|
3741
|
+
amdEvaluationTimer = setInterval(() => {
|
|
3742
|
+
evaluateAmd();
|
|
3743
|
+
}, intervalMs);
|
|
3744
|
+
};
|
|
3745
|
+
const callSilenceTimeoutMs = options.callSilenceTimeoutMs && options.callSilenceTimeoutMs > 0 ? options.callSilenceTimeoutMs : undefined;
|
|
3746
|
+
let callSilenceWatchdog = null;
|
|
3747
|
+
let callSilenceFired = false;
|
|
3748
|
+
const clearCallSilenceWatchdog = () => {
|
|
3749
|
+
if (callSilenceWatchdog) {
|
|
3750
|
+
clearTimeout(callSilenceWatchdog);
|
|
3751
|
+
callSilenceWatchdog = null;
|
|
3752
|
+
}
|
|
3753
|
+
};
|
|
3754
|
+
const fireCallSilenceTimeout = () => {
|
|
3755
|
+
callSilenceWatchdog = null;
|
|
3756
|
+
if (callSilenceFired) {
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
callSilenceFired = true;
|
|
3760
|
+
api.close("silence-timeout");
|
|
3761
|
+
};
|
|
3762
|
+
const kickCallSilenceWatchdog = () => {
|
|
3763
|
+
if (callSilenceTimeoutMs === undefined || callSilenceFired) {
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
clearCallSilenceWatchdog();
|
|
3767
|
+
callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
|
|
3768
|
+
};
|
|
3685
3769
|
const recordingConfig = options.recording;
|
|
3686
3770
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
3687
3771
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -3970,6 +4054,7 @@ var createVoiceSession = (options) => {
|
|
|
3970
4054
|
const sendAssistantAudio = async (chunk, input) => {
|
|
3971
4055
|
const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
|
|
3972
4056
|
captureRecordingChunk("assistant", normalizedChunk, input.format);
|
|
4057
|
+
kickCallSilenceWatchdog();
|
|
3973
4058
|
await send({
|
|
3974
4059
|
chunkBase64: encodeBase64(normalizedChunk),
|
|
3975
4060
|
format: input.format,
|
|
@@ -4064,6 +4149,8 @@ var createVoiceSession = (options) => {
|
|
|
4064
4149
|
recoverable: false,
|
|
4065
4150
|
type: "error"
|
|
4066
4151
|
});
|
|
4152
|
+
clearCallSilenceWatchdog();
|
|
4153
|
+
clearAmdEvaluationTimer();
|
|
4067
4154
|
await closeTTSSession("failed");
|
|
4068
4155
|
await closeAdapter("failed");
|
|
4069
4156
|
await persistRecordings();
|
|
@@ -4133,6 +4220,8 @@ var createVoiceSession = (options) => {
|
|
|
4133
4220
|
sessionId: options.id,
|
|
4134
4221
|
type: "complete"
|
|
4135
4222
|
});
|
|
4223
|
+
clearCallSilenceWatchdog();
|
|
4224
|
+
clearAmdEvaluationTimer();
|
|
4136
4225
|
await closeTTSSession("complete");
|
|
4137
4226
|
await closeAdapter("complete");
|
|
4138
4227
|
await persistRecordings();
|
|
@@ -4191,19 +4280,21 @@ var createVoiceSession = (options) => {
|
|
|
4191
4280
|
});
|
|
4192
4281
|
};
|
|
4193
4282
|
const transferInternal = async (input) => {
|
|
4283
|
+
const transferMetadata = input.transferMode === undefined ? input.metadata : { ...input.metadata ?? {}, transferMode: input.transferMode };
|
|
4194
4284
|
const session = await writeSession((currentSession) => {
|
|
4195
4285
|
pushCallLifecycleEvent(currentSession, {
|
|
4196
|
-
metadata:
|
|
4286
|
+
metadata: transferMetadata,
|
|
4197
4287
|
reason: input.reason,
|
|
4198
4288
|
target: input.target,
|
|
4199
4289
|
type: "transfer"
|
|
4200
4290
|
});
|
|
4201
4291
|
});
|
|
4202
4292
|
await appendTrace({
|
|
4203
|
-
metadata:
|
|
4293
|
+
metadata: transferMetadata,
|
|
4204
4294
|
payload: {
|
|
4205
4295
|
reason: input.reason,
|
|
4206
4296
|
target: input.target,
|
|
4297
|
+
transferMode: input.transferMode,
|
|
4207
4298
|
type: "transfer"
|
|
4208
4299
|
},
|
|
4209
4300
|
session,
|
|
@@ -4988,6 +5079,7 @@ var createVoiceSession = (options) => {
|
|
|
4988
5079
|
};
|
|
4989
5080
|
const commitTurnInternal = async (reason = "manual") => {
|
|
4990
5081
|
clearSilenceTimer();
|
|
5082
|
+
amdLastTurnCommitAt = Date.now();
|
|
4991
5083
|
const session = await readSession();
|
|
4992
5084
|
if (session.status === "completed" || session.status === "failed") {
|
|
4993
5085
|
return;
|
|
@@ -5234,6 +5326,8 @@ var createVoiceSession = (options) => {
|
|
|
5234
5326
|
resumePendingTurnCommit(session);
|
|
5235
5327
|
await ensureAdapter();
|
|
5236
5328
|
warmTTSSession();
|
|
5329
|
+
kickCallSilenceWatchdog();
|
|
5330
|
+
startAmdEvaluationTimer();
|
|
5237
5331
|
};
|
|
5238
5332
|
const disconnectInternal = async (event) => {
|
|
5239
5333
|
clearSilenceTimer();
|
|
@@ -5281,12 +5375,17 @@ var createVoiceSession = (options) => {
|
|
|
5281
5375
|
const userBytes = conditionedAudio instanceof Uint8Array ? conditionedAudio : conditionedAudio instanceof ArrayBuffer ? new Uint8Array(conditionedAudio) : new Uint8Array(conditionedAudio.buffer, conditionedAudio.byteOffset, conditionedAudio.byteLength);
|
|
5282
5376
|
captureRecordingChunk("user", userBytes, recordingConfig.userInputFormat);
|
|
5283
5377
|
}
|
|
5378
|
+
amdLastAudioLevel = audioLevel;
|
|
5284
5379
|
if (audioLevel >= turnDetection.speechThreshold) {
|
|
5380
|
+
if (amdFirstAudioAt === undefined) {
|
|
5381
|
+
amdFirstAudioAt = Date.now();
|
|
5382
|
+
}
|
|
5285
5383
|
if (!speechDetected && activeTTSTurnId !== undefined) {
|
|
5286
5384
|
cancelActiveTTS("barge-in");
|
|
5287
5385
|
}
|
|
5288
5386
|
speechDetected = true;
|
|
5289
5387
|
clearSilenceTimer();
|
|
5388
|
+
kickCallSilenceWatchdog();
|
|
5290
5389
|
} else if (speechDetected) {
|
|
5291
5390
|
const currentSession = await readSession();
|
|
5292
5391
|
const hasTurnText = Boolean(buildTurnText(currentSession.currentTurn.transcripts, currentSession.currentTurn.partialText, {
|
|
@@ -5299,44 +5398,50 @@ var createVoiceSession = (options) => {
|
|
|
5299
5398
|
}
|
|
5300
5399
|
await adapter.send(conditionedAudio);
|
|
5301
5400
|
};
|
|
5401
|
+
const closeInternal = async (reason, disposition = "closed") => {
|
|
5402
|
+
const session = await writeSession((currentSession) => {
|
|
5403
|
+
if (currentSession.status !== "completed" && currentSession.status !== "failed" && !currentSession.call?.endedAt) {
|
|
5404
|
+
currentSession.lastActivityAt = Date.now();
|
|
5405
|
+
currentSession.status = "completed";
|
|
5406
|
+
pushCallLifecycleEvent(currentSession, {
|
|
5407
|
+
disposition,
|
|
5408
|
+
reason,
|
|
5409
|
+
type: "end"
|
|
5410
|
+
});
|
|
5411
|
+
}
|
|
5412
|
+
});
|
|
5413
|
+
clearSilenceTimer();
|
|
5414
|
+
clearCallSilenceWatchdog();
|
|
5415
|
+
clearAmdEvaluationTimer();
|
|
5416
|
+
await closeTTSSession(reason);
|
|
5417
|
+
await closeAdapter(reason);
|
|
5418
|
+
await persistRecordings();
|
|
5419
|
+
await Promise.resolve(socket.close(1000, reason));
|
|
5420
|
+
if (session.call?.endedAt && session.call.disposition === disposition) {
|
|
5421
|
+
await appendTrace({
|
|
5422
|
+
payload: {
|
|
5423
|
+
disposition,
|
|
5424
|
+
reason,
|
|
5425
|
+
type: "end"
|
|
5426
|
+
},
|
|
5427
|
+
session,
|
|
5428
|
+
type: "call.lifecycle"
|
|
5429
|
+
});
|
|
5430
|
+
await options.route.onCallEnd?.({
|
|
5431
|
+
api,
|
|
5432
|
+
context: options.context,
|
|
5433
|
+
disposition,
|
|
5434
|
+
reason,
|
|
5435
|
+
session
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5438
|
+
};
|
|
5302
5439
|
const api = {
|
|
5303
5440
|
id: options.id,
|
|
5304
5441
|
close: async (reason) => {
|
|
5305
5442
|
await runSerial("api.close", async () => {
|
|
5306
|
-
const
|
|
5307
|
-
|
|
5308
|
-
currentSession.lastActivityAt = Date.now();
|
|
5309
|
-
currentSession.status = "completed";
|
|
5310
|
-
pushCallLifecycleEvent(currentSession, {
|
|
5311
|
-
disposition: "closed",
|
|
5312
|
-
reason,
|
|
5313
|
-
type: "end"
|
|
5314
|
-
});
|
|
5315
|
-
}
|
|
5316
|
-
});
|
|
5317
|
-
clearSilenceTimer();
|
|
5318
|
-
await closeTTSSession(reason);
|
|
5319
|
-
await closeAdapter(reason);
|
|
5320
|
-
await persistRecordings();
|
|
5321
|
-
await Promise.resolve(socket.close(1000, reason));
|
|
5322
|
-
if (session.call?.endedAt && session.call.disposition === "closed") {
|
|
5323
|
-
await appendTrace({
|
|
5324
|
-
payload: {
|
|
5325
|
-
disposition: "closed",
|
|
5326
|
-
reason,
|
|
5327
|
-
type: "end"
|
|
5328
|
-
},
|
|
5329
|
-
session,
|
|
5330
|
-
type: "call.lifecycle"
|
|
5331
|
-
});
|
|
5332
|
-
await options.route.onCallEnd?.({
|
|
5333
|
-
api,
|
|
5334
|
-
context: options.context,
|
|
5335
|
-
disposition: "closed",
|
|
5336
|
-
reason,
|
|
5337
|
-
session
|
|
5338
|
-
});
|
|
5339
|
-
}
|
|
5443
|
+
const disposition = reason === "silence-timeout" ? "silence-timeout" : "closed";
|
|
5444
|
+
await closeInternal(reason, disposition);
|
|
5340
5445
|
});
|
|
5341
5446
|
},
|
|
5342
5447
|
commitTurn: async (reason = "manual") => runSerial("api.commitTurn", async () => {
|
|
@@ -9082,6 +9187,18 @@ var RECIPE_DEFAULTS = {
|
|
|
9082
9187
|
defaultQueue: "transfer-verification",
|
|
9083
9188
|
description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
|
|
9084
9189
|
escalationQueue: "transfer-escalations"
|
|
9190
|
+
},
|
|
9191
|
+
"cold-transfer": {
|
|
9192
|
+
completedAction: "Verify the SIP REFER landed and the caller reached the destination without re-introduction.",
|
|
9193
|
+
completedDescription: "The call was cold-transferred (SIP REFER) \u2014 confirm the destination picked up.",
|
|
9194
|
+
completedKind: "transfer-check",
|
|
9195
|
+
completedTitle: "Verify cold transfer",
|
|
9196
|
+
defaultCompletedCreatesTask: false,
|
|
9197
|
+
defaultDueInMs: 5 * 60000,
|
|
9198
|
+
defaultPriority: "normal",
|
|
9199
|
+
defaultQueue: "transfer-verification",
|
|
9200
|
+
description: "Creates verification work for cold-transferred (REFER) calls and escalation work when the handoff fails.",
|
|
9201
|
+
escalationQueue: "transfer-escalations"
|
|
9085
9202
|
}
|
|
9086
9203
|
};
|
|
9087
9204
|
var buildRecipeTask = (input) => {
|
|
@@ -9211,7 +9328,7 @@ var resolveVoiceOutcomeRecipe = (name, options = {}) => {
|
|
|
9211
9328
|
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
|
|
9212
9329
|
name: `${name}-transfer-check`,
|
|
9213
9330
|
priority: options.priority ?? defaults.defaultPriority,
|
|
9214
|
-
queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
9331
|
+
queue: name === "warm-transfer" || name === "cold-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
9215
9332
|
},
|
|
9216
9333
|
voicemail: {
|
|
9217
9334
|
assignee: options.assignee,
|
|
@@ -34818,6 +34935,36 @@ var createAIVoiceModel = (options) => ({
|
|
|
34818
34935
|
return output;
|
|
34819
34936
|
}
|
|
34820
34937
|
});
|
|
34938
|
+
// src/amdDetector.ts
|
|
34939
|
+
var createMonologueAMDDetector = (options = {}) => {
|
|
34940
|
+
const minMonologueMs = options.minMonologueMs ?? 8000;
|
|
34941
|
+
const reason = options.reason ?? "monologue-suspected-voicemail";
|
|
34942
|
+
const requireFirstAudio = options.requireFirstAudio ?? true;
|
|
34943
|
+
return {
|
|
34944
|
+
evaluate: ({
|
|
34945
|
+
elapsedSinceFirstAudioMs,
|
|
34946
|
+
elapsedSinceLastTurnCommitMs,
|
|
34947
|
+
session
|
|
34948
|
+
}) => {
|
|
34949
|
+
if (requireFirstAudio && elapsedSinceFirstAudioMs <= 0) {
|
|
34950
|
+
return;
|
|
34951
|
+
}
|
|
34952
|
+
const noTurnsYet = session.turns.length === 0;
|
|
34953
|
+
const monologueElapsed = noTurnsYet ? elapsedSinceFirstAudioMs : elapsedSinceLastTurnCommitMs;
|
|
34954
|
+
if (monologueElapsed < minMonologueMs) {
|
|
34955
|
+
return;
|
|
34956
|
+
}
|
|
34957
|
+
return {
|
|
34958
|
+
metadata: {
|
|
34959
|
+
detector: "monologue",
|
|
34960
|
+
monologueMs: monologueElapsed
|
|
34961
|
+
},
|
|
34962
|
+
reason
|
|
34963
|
+
};
|
|
34964
|
+
},
|
|
34965
|
+
intervalMs: options.intervalMs ?? 1000
|
|
34966
|
+
};
|
|
34967
|
+
};
|
|
34821
34968
|
// src/ragTool.ts
|
|
34822
34969
|
var DEFAULT_TOOL_NAME = "searchKnowledgeBase";
|
|
34823
34970
|
var DEFAULT_DESCRIPTION = "Search the knowledge base and return short grounded citations. Use this whenever the caller asks a question that may be answered by indexed reference material.";
|
|
@@ -34999,7 +35146,8 @@ ${destinationDocs}`,
|
|
|
34999
35146
|
metadata: destination.metadata,
|
|
35000
35147
|
reason: args?.reason,
|
|
35001
35148
|
result,
|
|
35002
|
-
target: destination.target
|
|
35149
|
+
target: destination.target,
|
|
35150
|
+
transferMode: destination.transferMode
|
|
35003
35151
|
});
|
|
35004
35152
|
return {
|
|
35005
35153
|
destinationId: destination.id,
|
|
@@ -41255,6 +41403,62 @@ var createVoiceS3ReviewStore = (options) => {
|
|
|
41255
41403
|
set
|
|
41256
41404
|
};
|
|
41257
41405
|
};
|
|
41406
|
+
var normalizeRecordingKeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/recordings";
|
|
41407
|
+
var recordingWavKey = (prefix, sessionId, channel) => `${prefix}/${encodeURIComponent(sessionId)}_${channel}.wav`;
|
|
41408
|
+
var recordingMetadataKey = (prefix, sessionId, channel) => `${prefix}/${encodeURIComponent(sessionId)}_${channel}.json`;
|
|
41409
|
+
var createVoiceS3RecordingStore = (options) => {
|
|
41410
|
+
const client = options.client ?? new Bun.S3Client(options);
|
|
41411
|
+
const keyPrefix = normalizeRecordingKeyPrefix(options.keyPrefix);
|
|
41412
|
+
const publicUrlBase = options.publicUrlBase?.replace(/\/+$/, "");
|
|
41413
|
+
const getFile = (key) => client.file(key, options);
|
|
41414
|
+
const resolveUrl = (key) => publicUrlBase ? `${publicUrlBase}/${key}` : `s3://${key}`;
|
|
41415
|
+
const put = async (artifact) => {
|
|
41416
|
+
const wavKey = recordingWavKey(keyPrefix, artifact.sessionId, artifact.channel);
|
|
41417
|
+
const metadataKey = recordingMetadataKey(keyPrefix, artifact.sessionId, artifact.channel);
|
|
41418
|
+
const wav = encodePcmAsWav(artifact.audioBytes, artifact.format);
|
|
41419
|
+
await getFile(wavKey).write(wav);
|
|
41420
|
+
const recordingUrl = resolveUrl(wavKey);
|
|
41421
|
+
const metadata = {
|
|
41422
|
+
capturedAt: artifact.capturedAt,
|
|
41423
|
+
channel: artifact.channel,
|
|
41424
|
+
durationMs: artifact.durationMs,
|
|
41425
|
+
format: artifact.format,
|
|
41426
|
+
recordingUrl,
|
|
41427
|
+
sessionId: artifact.sessionId
|
|
41428
|
+
};
|
|
41429
|
+
await getFile(metadataKey).write(JSON.stringify(metadata));
|
|
41430
|
+
return {
|
|
41431
|
+
...artifact,
|
|
41432
|
+
recordingUrl
|
|
41433
|
+
};
|
|
41434
|
+
};
|
|
41435
|
+
const readMetadata = async (sessionId, channel) => {
|
|
41436
|
+
const metadataKey = recordingMetadataKey(keyPrefix, sessionId, channel);
|
|
41437
|
+
const wavKey = recordingWavKey(keyPrefix, sessionId, channel);
|
|
41438
|
+
const metadataFile = getFile(metadataKey);
|
|
41439
|
+
if (!await metadataFile.exists()) {
|
|
41440
|
+
return;
|
|
41441
|
+
}
|
|
41442
|
+
const meta = JSON.parse(await metadataFile.text());
|
|
41443
|
+
const wavBytes = await getFile(wavKey).bytes();
|
|
41444
|
+
return {
|
|
41445
|
+
audioBytes: wavBytes,
|
|
41446
|
+
capturedAt: meta.capturedAt,
|
|
41447
|
+
channel: meta.channel,
|
|
41448
|
+
durationMs: meta.durationMs,
|
|
41449
|
+
format: meta.format,
|
|
41450
|
+
recordingUrl: meta.recordingUrl,
|
|
41451
|
+
sessionId: meta.sessionId
|
|
41452
|
+
};
|
|
41453
|
+
};
|
|
41454
|
+
const get = (sessionId, channel) => readMetadata(sessionId, channel);
|
|
41455
|
+
const list = async (sessionId) => {
|
|
41456
|
+
const channels = ["assistant", "user"];
|
|
41457
|
+
const records = await Promise.all(channels.map((channel) => readMetadata(sessionId, channel)));
|
|
41458
|
+
return records.filter((record) => record !== undefined);
|
|
41459
|
+
};
|
|
41460
|
+
return { get, list, put };
|
|
41461
|
+
};
|
|
41258
41462
|
// src/memoryStore.ts
|
|
41259
41463
|
var createVoiceMemoryStore = () => {
|
|
41260
41464
|
const sessions = new Map;
|
|
@@ -45687,6 +45891,7 @@ export {
|
|
|
45687
45891
|
createVoiceSQLiteAuditSinkDeliveryStore,
|
|
45688
45892
|
createVoiceSQLiteAuditEventStore,
|
|
45689
45893
|
createVoiceS3ReviewStore,
|
|
45894
|
+
createVoiceS3RecordingStore,
|
|
45690
45895
|
createVoiceS3DeliverySink,
|
|
45691
45896
|
createVoiceRoutingDecisionSummary,
|
|
45692
45897
|
createVoiceReviewSavedEvent,
|
|
@@ -45928,6 +46133,7 @@ export {
|
|
|
45928
46133
|
createPhraseHintCorrectionHandler,
|
|
45929
46134
|
createOpenAIVoiceTTS,
|
|
45930
46135
|
createOpenAIVoiceAssistantModel,
|
|
46136
|
+
createMonologueAMDDetector,
|
|
45931
46137
|
createMemoryVoiceTelnyxWebhookEventStore,
|
|
45932
46138
|
createMemoryVoiceTelephonyWebhookIdempotencyStore,
|
|
45933
46139
|
createMemoryVoicePlivoWebhookNonceStore,
|
package/dist/outcomeRecipes.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VoiceOpsTaskPriority } from "./ops";
|
|
2
2
|
import type { VoiceRuntimeOpsConfig, VoiceSessionRecord } from "./types";
|
|
3
|
-
export type VoiceOutcomeRecipeName = "appointment-booking" | "lead-qualification" | "support-triage" | "voicemail-callback" | "warm-transfer";
|
|
3
|
+
export type VoiceOutcomeRecipeName = "appointment-booking" | "cold-transfer" | "lead-qualification" | "support-triage" | "voicemail-callback" | "warm-transfer";
|
|
4
4
|
export type VoiceOutcomeRecipeOptions = {
|
|
5
5
|
assignee?: string;
|
|
6
6
|
completedCreatesTask?: boolean;
|
package/dist/s3Store.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { S3Client, S3Options } from "bun";
|
|
2
2
|
import type { StoredVoiceCallReviewArtifact, VoiceCallReviewStore } from "./testing/review";
|
|
3
|
+
import type { VoiceRecordingStore } from "./recordingStore";
|
|
3
4
|
export type VoiceS3ReviewStoreFile = {
|
|
4
5
|
delete: () => Promise<void>;
|
|
5
6
|
exists: () => Promise<boolean>;
|
|
@@ -12,3 +13,17 @@ export type VoiceS3ReviewStoreOptions = S3Options & {
|
|
|
12
13
|
keyPrefix?: string;
|
|
13
14
|
};
|
|
14
15
|
export declare const createVoiceS3ReviewStore: <TArtifact extends StoredVoiceCallReviewArtifact = StoredVoiceCallReviewArtifact>(options: VoiceS3ReviewStoreOptions) => VoiceCallReviewStore<TArtifact>;
|
|
16
|
+
export type VoiceS3RecordingStoreFile = {
|
|
17
|
+
delete: () => Promise<void>;
|
|
18
|
+
exists: () => Promise<boolean>;
|
|
19
|
+
text: () => Promise<string>;
|
|
20
|
+
bytes: () => Promise<Uint8Array>;
|
|
21
|
+
write: (data: string | Uint8Array) => Promise<number>;
|
|
22
|
+
};
|
|
23
|
+
export type VoiceS3RecordingStoreClient = Pick<S3Client, "file" | "list">;
|
|
24
|
+
export type VoiceS3RecordingStoreOptions = S3Options & {
|
|
25
|
+
client?: VoiceS3RecordingStoreClient;
|
|
26
|
+
keyPrefix?: string;
|
|
27
|
+
publicUrlBase?: string;
|
|
28
|
+
};
|
|
29
|
+
export declare const createVoiceS3RecordingStore: (options: VoiceS3RecordingStoreOptions) => VoiceRecordingStore;
|
package/dist/testing/index.js
CHANGED
|
@@ -5650,6 +5650,90 @@ var createVoiceSession = (options) => {
|
|
|
5650
5650
|
const currentTurnAudio = [];
|
|
5651
5651
|
let fallbackAttemptsForCurrentTurn = 0;
|
|
5652
5652
|
let fallbackReplayAudioMsForCurrentTurn = 0;
|
|
5653
|
+
const amdDetector = options.amd;
|
|
5654
|
+
let amdEvaluationTimer = null;
|
|
5655
|
+
let amdFired = false;
|
|
5656
|
+
let amdFirstAudioAt;
|
|
5657
|
+
let amdLastTurnCommitAt;
|
|
5658
|
+
let amdLastAudioLevel;
|
|
5659
|
+
const clearAmdEvaluationTimer = () => {
|
|
5660
|
+
if (amdEvaluationTimer) {
|
|
5661
|
+
clearInterval(amdEvaluationTimer);
|
|
5662
|
+
amdEvaluationTimer = null;
|
|
5663
|
+
}
|
|
5664
|
+
};
|
|
5665
|
+
const evaluateAmd = async () => {
|
|
5666
|
+
if (!amdDetector || amdFired) {
|
|
5667
|
+
return;
|
|
5668
|
+
}
|
|
5669
|
+
let snapshot;
|
|
5670
|
+
try {
|
|
5671
|
+
snapshot = await readSession();
|
|
5672
|
+
} catch {
|
|
5673
|
+
return;
|
|
5674
|
+
}
|
|
5675
|
+
const now = Date.now();
|
|
5676
|
+
const verdict = await Promise.resolve(amdDetector.evaluate({
|
|
5677
|
+
api,
|
|
5678
|
+
audioLevel: amdLastAudioLevel,
|
|
5679
|
+
elapsedSinceFirstAudioMs: amdFirstAudioAt === undefined ? 0 : now - amdFirstAudioAt,
|
|
5680
|
+
elapsedSinceLastTurnCommitMs: amdLastTurnCommitAt === undefined ? 0 : now - amdLastTurnCommitAt,
|
|
5681
|
+
partialTranscript: snapshot.currentTurn.partialText,
|
|
5682
|
+
session: snapshot,
|
|
5683
|
+
transcripts: [
|
|
5684
|
+
...snapshot.transcripts,
|
|
5685
|
+
...snapshot.currentTurn.transcripts
|
|
5686
|
+
]
|
|
5687
|
+
}));
|
|
5688
|
+
if (!verdict || amdFired) {
|
|
5689
|
+
return;
|
|
5690
|
+
}
|
|
5691
|
+
amdFired = true;
|
|
5692
|
+
clearAmdEvaluationTimer();
|
|
5693
|
+
try {
|
|
5694
|
+
await api.markVoicemail({
|
|
5695
|
+
metadata: verdict.metadata
|
|
5696
|
+
});
|
|
5697
|
+
} catch (error) {
|
|
5698
|
+
logger.warn("voice amd markVoicemail failed", {
|
|
5699
|
+
error: toError(error).message,
|
|
5700
|
+
sessionId: options.id
|
|
5701
|
+
});
|
|
5702
|
+
}
|
|
5703
|
+
};
|
|
5704
|
+
const startAmdEvaluationTimer = () => {
|
|
5705
|
+
if (!amdDetector || amdEvaluationTimer || amdFired) {
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
const intervalMs = amdDetector.intervalMs ?? 1000;
|
|
5709
|
+
amdEvaluationTimer = setInterval(() => {
|
|
5710
|
+
evaluateAmd();
|
|
5711
|
+
}, intervalMs);
|
|
5712
|
+
};
|
|
5713
|
+
const callSilenceTimeoutMs = options.callSilenceTimeoutMs && options.callSilenceTimeoutMs > 0 ? options.callSilenceTimeoutMs : undefined;
|
|
5714
|
+
let callSilenceWatchdog = null;
|
|
5715
|
+
let callSilenceFired = false;
|
|
5716
|
+
const clearCallSilenceWatchdog = () => {
|
|
5717
|
+
if (callSilenceWatchdog) {
|
|
5718
|
+
clearTimeout(callSilenceWatchdog);
|
|
5719
|
+
callSilenceWatchdog = null;
|
|
5720
|
+
}
|
|
5721
|
+
};
|
|
5722
|
+
const fireCallSilenceTimeout = () => {
|
|
5723
|
+
callSilenceWatchdog = null;
|
|
5724
|
+
if (callSilenceFired) {
|
|
5725
|
+
return;
|
|
5726
|
+
}
|
|
5727
|
+
callSilenceFired = true;
|
|
5728
|
+
api.close("silence-timeout");
|
|
5729
|
+
};
|
|
5730
|
+
const kickCallSilenceWatchdog = () => {
|
|
5731
|
+
if (callSilenceTimeoutMs === undefined || callSilenceFired) {
|
|
5732
|
+
return;
|
|
5733
|
+
}
|
|
5734
|
+
clearCallSilenceWatchdog();
|
|
5735
|
+
callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
|
|
5736
|
+
};
|
|
5653
5737
|
const recordingConfig = options.recording;
|
|
5654
5738
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
5655
5739
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -5938,6 +6022,7 @@ var createVoiceSession = (options) => {
|
|
|
5938
6022
|
const sendAssistantAudio = async (chunk, input) => {
|
|
5939
6023
|
const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
|
|
5940
6024
|
captureRecordingChunk("assistant", normalizedChunk, input.format);
|
|
6025
|
+
kickCallSilenceWatchdog();
|
|
5941
6026
|
await send({
|
|
5942
6027
|
chunkBase64: encodeBase64(normalizedChunk),
|
|
5943
6028
|
format: input.format,
|
|
@@ -6032,6 +6117,8 @@ var createVoiceSession = (options) => {
|
|
|
6032
6117
|
recoverable: false,
|
|
6033
6118
|
type: "error"
|
|
6034
6119
|
});
|
|
6120
|
+
clearCallSilenceWatchdog();
|
|
6121
|
+
clearAmdEvaluationTimer();
|
|
6035
6122
|
await closeTTSSession("failed");
|
|
6036
6123
|
await closeAdapter("failed");
|
|
6037
6124
|
await persistRecordings();
|
|
@@ -6101,6 +6188,8 @@ var createVoiceSession = (options) => {
|
|
|
6101
6188
|
sessionId: options.id,
|
|
6102
6189
|
type: "complete"
|
|
6103
6190
|
});
|
|
6191
|
+
clearCallSilenceWatchdog();
|
|
6192
|
+
clearAmdEvaluationTimer();
|
|
6104
6193
|
await closeTTSSession("complete");
|
|
6105
6194
|
await closeAdapter("complete");
|
|
6106
6195
|
await persistRecordings();
|
|
@@ -6159,19 +6248,21 @@ var createVoiceSession = (options) => {
|
|
|
6159
6248
|
});
|
|
6160
6249
|
};
|
|
6161
6250
|
const transferInternal = async (input) => {
|
|
6251
|
+
const transferMetadata = input.transferMode === undefined ? input.metadata : { ...input.metadata ?? {}, transferMode: input.transferMode };
|
|
6162
6252
|
const session = await writeSession((currentSession) => {
|
|
6163
6253
|
pushCallLifecycleEvent(currentSession, {
|
|
6164
|
-
metadata:
|
|
6254
|
+
metadata: transferMetadata,
|
|
6165
6255
|
reason: input.reason,
|
|
6166
6256
|
target: input.target,
|
|
6167
6257
|
type: "transfer"
|
|
6168
6258
|
});
|
|
6169
6259
|
});
|
|
6170
6260
|
await appendTrace({
|
|
6171
|
-
metadata:
|
|
6261
|
+
metadata: transferMetadata,
|
|
6172
6262
|
payload: {
|
|
6173
6263
|
reason: input.reason,
|
|
6174
6264
|
target: input.target,
|
|
6265
|
+
transferMode: input.transferMode,
|
|
6175
6266
|
type: "transfer"
|
|
6176
6267
|
},
|
|
6177
6268
|
session,
|
|
@@ -6956,6 +7047,7 @@ var createVoiceSession = (options) => {
|
|
|
6956
7047
|
};
|
|
6957
7048
|
const commitTurnInternal = async (reason = "manual") => {
|
|
6958
7049
|
clearSilenceTimer();
|
|
7050
|
+
amdLastTurnCommitAt = Date.now();
|
|
6959
7051
|
const session = await readSession();
|
|
6960
7052
|
if (session.status === "completed" || session.status === "failed") {
|
|
6961
7053
|
return;
|
|
@@ -7202,6 +7294,8 @@ var createVoiceSession = (options) => {
|
|
|
7202
7294
|
resumePendingTurnCommit(session);
|
|
7203
7295
|
await ensureAdapter();
|
|
7204
7296
|
warmTTSSession();
|
|
7297
|
+
kickCallSilenceWatchdog();
|
|
7298
|
+
startAmdEvaluationTimer();
|
|
7205
7299
|
};
|
|
7206
7300
|
const disconnectInternal = async (event) => {
|
|
7207
7301
|
clearSilenceTimer();
|
|
@@ -7249,12 +7343,17 @@ var createVoiceSession = (options) => {
|
|
|
7249
7343
|
const userBytes = conditionedAudio instanceof Uint8Array ? conditionedAudio : conditionedAudio instanceof ArrayBuffer ? new Uint8Array(conditionedAudio) : new Uint8Array(conditionedAudio.buffer, conditionedAudio.byteOffset, conditionedAudio.byteLength);
|
|
7250
7344
|
captureRecordingChunk("user", userBytes, recordingConfig.userInputFormat);
|
|
7251
7345
|
}
|
|
7346
|
+
amdLastAudioLevel = audioLevel;
|
|
7252
7347
|
if (audioLevel >= turnDetection.speechThreshold) {
|
|
7348
|
+
if (amdFirstAudioAt === undefined) {
|
|
7349
|
+
amdFirstAudioAt = Date.now();
|
|
7350
|
+
}
|
|
7253
7351
|
if (!speechDetected && activeTTSTurnId !== undefined) {
|
|
7254
7352
|
cancelActiveTTS("barge-in");
|
|
7255
7353
|
}
|
|
7256
7354
|
speechDetected = true;
|
|
7257
7355
|
clearSilenceTimer();
|
|
7356
|
+
kickCallSilenceWatchdog();
|
|
7258
7357
|
} else if (speechDetected) {
|
|
7259
7358
|
const currentSession = await readSession();
|
|
7260
7359
|
const hasTurnText = Boolean(buildTurnText(currentSession.currentTurn.transcripts, currentSession.currentTurn.partialText, {
|
|
@@ -7267,44 +7366,50 @@ var createVoiceSession = (options) => {
|
|
|
7267
7366
|
}
|
|
7268
7367
|
await adapter.send(conditionedAudio);
|
|
7269
7368
|
};
|
|
7369
|
+
const closeInternal = async (reason, disposition = "closed") => {
|
|
7370
|
+
const session = await writeSession((currentSession) => {
|
|
7371
|
+
if (currentSession.status !== "completed" && currentSession.status !== "failed" && !currentSession.call?.endedAt) {
|
|
7372
|
+
currentSession.lastActivityAt = Date.now();
|
|
7373
|
+
currentSession.status = "completed";
|
|
7374
|
+
pushCallLifecycleEvent(currentSession, {
|
|
7375
|
+
disposition,
|
|
7376
|
+
reason,
|
|
7377
|
+
type: "end"
|
|
7378
|
+
});
|
|
7379
|
+
}
|
|
7380
|
+
});
|
|
7381
|
+
clearSilenceTimer();
|
|
7382
|
+
clearCallSilenceWatchdog();
|
|
7383
|
+
clearAmdEvaluationTimer();
|
|
7384
|
+
await closeTTSSession(reason);
|
|
7385
|
+
await closeAdapter(reason);
|
|
7386
|
+
await persistRecordings();
|
|
7387
|
+
await Promise.resolve(socket.close(1000, reason));
|
|
7388
|
+
if (session.call?.endedAt && session.call.disposition === disposition) {
|
|
7389
|
+
await appendTrace({
|
|
7390
|
+
payload: {
|
|
7391
|
+
disposition,
|
|
7392
|
+
reason,
|
|
7393
|
+
type: "end"
|
|
7394
|
+
},
|
|
7395
|
+
session,
|
|
7396
|
+
type: "call.lifecycle"
|
|
7397
|
+
});
|
|
7398
|
+
await options.route.onCallEnd?.({
|
|
7399
|
+
api,
|
|
7400
|
+
context: options.context,
|
|
7401
|
+
disposition,
|
|
7402
|
+
reason,
|
|
7403
|
+
session
|
|
7404
|
+
});
|
|
7405
|
+
}
|
|
7406
|
+
};
|
|
7270
7407
|
const api = {
|
|
7271
7408
|
id: options.id,
|
|
7272
7409
|
close: async (reason) => {
|
|
7273
7410
|
await runSerial("api.close", async () => {
|
|
7274
|
-
const
|
|
7275
|
-
|
|
7276
|
-
currentSession.lastActivityAt = Date.now();
|
|
7277
|
-
currentSession.status = "completed";
|
|
7278
|
-
pushCallLifecycleEvent(currentSession, {
|
|
7279
|
-
disposition: "closed",
|
|
7280
|
-
reason,
|
|
7281
|
-
type: "end"
|
|
7282
|
-
});
|
|
7283
|
-
}
|
|
7284
|
-
});
|
|
7285
|
-
clearSilenceTimer();
|
|
7286
|
-
await closeTTSSession(reason);
|
|
7287
|
-
await closeAdapter(reason);
|
|
7288
|
-
await persistRecordings();
|
|
7289
|
-
await Promise.resolve(socket.close(1000, reason));
|
|
7290
|
-
if (session.call?.endedAt && session.call.disposition === "closed") {
|
|
7291
|
-
await appendTrace({
|
|
7292
|
-
payload: {
|
|
7293
|
-
disposition: "closed",
|
|
7294
|
-
reason,
|
|
7295
|
-
type: "end"
|
|
7296
|
-
},
|
|
7297
|
-
session,
|
|
7298
|
-
type: "call.lifecycle"
|
|
7299
|
-
});
|
|
7300
|
-
await options.route.onCallEnd?.({
|
|
7301
|
-
api,
|
|
7302
|
-
context: options.context,
|
|
7303
|
-
disposition: "closed",
|
|
7304
|
-
reason,
|
|
7305
|
-
session
|
|
7306
|
-
});
|
|
7307
|
-
}
|
|
7411
|
+
const disposition = reason === "silence-timeout" ? "silence-timeout" : "closed";
|
|
7412
|
+
await closeInternal(reason, disposition);
|
|
7308
7413
|
});
|
|
7309
7414
|
},
|
|
7310
7415
|
commitTurn: async (reason = "manual") => runSerial("api.commitTurn", async () => {
|
package/dist/types.d.ts
CHANGED
|
@@ -271,7 +271,7 @@ export type VoiceSessionSummary = {
|
|
|
271
271
|
status: VoiceSessionStatus;
|
|
272
272
|
turnCount: number;
|
|
273
273
|
};
|
|
274
|
-
export type VoiceCallDisposition = "completed" | "transferred" | "escalated" | "voicemail" | "no-answer" | "failed" | "closed";
|
|
274
|
+
export type VoiceCallDisposition = "completed" | "transferred" | "escalated" | "voicemail" | "no-answer" | "failed" | "silence-timeout" | "closed";
|
|
275
275
|
export type VoiceCallLifecycleEvent = {
|
|
276
276
|
at: number;
|
|
277
277
|
type: "start" | "end" | "transfer" | "escalation" | "voicemail" | "no-answer";
|
|
@@ -455,6 +455,7 @@ export type VoiceSessionHandle<TContext = unknown, TSession extends VoiceSession
|
|
|
455
455
|
reason?: string;
|
|
456
456
|
result?: TResult;
|
|
457
457
|
target: string;
|
|
458
|
+
transferMode?: "cold" | "warm";
|
|
458
459
|
}) => Promise<void>;
|
|
459
460
|
close: (reason?: string) => Promise<void>;
|
|
460
461
|
snapshot: () => Promise<TSession>;
|
|
@@ -467,6 +468,7 @@ export type VoiceRouteResult<TResult = unknown> = {
|
|
|
467
468
|
metadata?: Record<string, unknown>;
|
|
468
469
|
reason?: string;
|
|
469
470
|
target: string;
|
|
471
|
+
transferMode?: "cold" | "warm";
|
|
470
472
|
};
|
|
471
473
|
escalate?: {
|
|
472
474
|
metadata?: Record<string, unknown>;
|
|
@@ -722,6 +724,8 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
722
724
|
store: VoiceSessionStore<TSession>;
|
|
723
725
|
trace?: VoiceTraceEventStore;
|
|
724
726
|
recording?: VoiceSessionRecordingConfig;
|
|
727
|
+
callSilenceTimeoutMs?: number;
|
|
728
|
+
amd?: import("./amdDetector").VoiceAMDDetector<TContext, TSession, TResult>;
|
|
725
729
|
reconnect: Required<VoiceReconnectConfig>;
|
|
726
730
|
phraseHints?: VoicePhraseHint[];
|
|
727
731
|
sessionMetadata?: Record<string, unknown>;
|