@absolutejs/voice 0.0.22-beta.477 → 0.0.22-beta.478
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/index.d.ts +1 -1
- package/dist/index.js +142 -38
- package/dist/outcomeRecipes.d.ts +1 -1
- package/dist/s3Store.d.ts +15 -0
- package/dist/testing/index.js +70 -36
- package/dist/types.d.ts +4 -1
- package/package.json +1 -1
package/dist/agentTools.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export { buildVoiceTraceDeliveryReport, createVoiceTraceDeliveryHTMLHandler, cre
|
|
|
120
120
|
export { createVoiceTraceTimelineRoutes, renderVoiceTraceTimelineHTML, renderVoiceTraceTimelineSessionHTML, summarizeVoiceTraceTimeline, } from "./traceTimeline";
|
|
121
121
|
export { createVoiceSQLiteAuditEventStore, createVoiceSQLiteAuditSinkDeliveryStore, createVoiceSQLiteCampaignStore, createVoiceSQLiteExternalObjectMapStore, createVoiceSQLiteIntegrationEventStore, createVoiceSQLiteReviewStore, createVoiceSQLiteRuntimeStorage, createVoiceSQLiteSessionStore, createVoiceSQLiteTaskStore, createVoiceSQLiteTelephonyWebhookIdempotencyStore, createVoiceSQLiteTraceSinkDeliveryStore, createVoiceSQLiteTraceEventStore, } from "./sqliteStore";
|
|
122
122
|
export { createVoicePostgresAuditEventStore, createVoicePostgresAuditSinkDeliveryStore, createVoicePostgresCampaignStore, createVoicePostgresExternalObjectMapStore, createVoicePostgresIntegrationEventStore, createVoicePostgresReviewStore, createVoicePostgresRuntimeStorage, createVoicePostgresSessionStore, createVoicePostgresTaskStore, createVoicePostgresTelephonyWebhookIdempotencyStore, createVoicePostgresTraceSinkDeliveryStore, createVoicePostgresTraceEventStore, } from "./postgresStore";
|
|
123
|
-
export { createVoiceS3ReviewStore } from "./s3Store";
|
|
123
|
+
export { createVoiceS3RecordingStore, createVoiceS3ReviewStore } from "./s3Store";
|
|
124
124
|
export { createVoiceMemoryStore } from "./memoryStore";
|
|
125
125
|
export { createVoiceCRMActivitySink, createVoiceHelpdeskTicketSink, createVoiceIntegrationHTTPSink, createVoiceHubSpotTaskSink, createVoiceHubSpotTaskSyncSinks, createVoiceHubSpotTaskUpdateSink, createVoiceLinearIssueSink, createVoiceLinearIssueSyncSinks, createVoiceLinearIssueUpdateSink, createVoiceZendeskTicketSink, createVoiceZendeskTicketSyncSinks, createVoiceZendeskTicketUpdateSink, deliverVoiceIntegrationEventToSinks, } from "./opsSinks";
|
|
126
126
|
export { createVoiceOpsWebhookEnvelope, createVoiceOpsWebhookReceiverRoutes, createVoiceOpsWebhookSink, verifyVoiceOpsWebhookSignature, } from "./opsWebhook";
|
package/dist/index.js
CHANGED
|
@@ -3682,6 +3682,30 @@ var createVoiceSession = (options) => {
|
|
|
3682
3682
|
const currentTurnAudio = [];
|
|
3683
3683
|
let fallbackAttemptsForCurrentTurn = 0;
|
|
3684
3684
|
let fallbackReplayAudioMsForCurrentTurn = 0;
|
|
3685
|
+
const callSilenceTimeoutMs = options.callSilenceTimeoutMs && options.callSilenceTimeoutMs > 0 ? options.callSilenceTimeoutMs : undefined;
|
|
3686
|
+
let callSilenceWatchdog = null;
|
|
3687
|
+
let callSilenceFired = false;
|
|
3688
|
+
const clearCallSilenceWatchdog = () => {
|
|
3689
|
+
if (callSilenceWatchdog) {
|
|
3690
|
+
clearTimeout(callSilenceWatchdog);
|
|
3691
|
+
callSilenceWatchdog = null;
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
const fireCallSilenceTimeout = () => {
|
|
3695
|
+
callSilenceWatchdog = null;
|
|
3696
|
+
if (callSilenceFired) {
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
callSilenceFired = true;
|
|
3700
|
+
api.close("silence-timeout");
|
|
3701
|
+
};
|
|
3702
|
+
const kickCallSilenceWatchdog = () => {
|
|
3703
|
+
if (callSilenceTimeoutMs === undefined || callSilenceFired) {
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
clearCallSilenceWatchdog();
|
|
3707
|
+
callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
|
|
3708
|
+
};
|
|
3685
3709
|
const recordingConfig = options.recording;
|
|
3686
3710
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
3687
3711
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -3970,6 +3994,7 @@ var createVoiceSession = (options) => {
|
|
|
3970
3994
|
const sendAssistantAudio = async (chunk, input) => {
|
|
3971
3995
|
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
3996
|
captureRecordingChunk("assistant", normalizedChunk, input.format);
|
|
3997
|
+
kickCallSilenceWatchdog();
|
|
3973
3998
|
await send({
|
|
3974
3999
|
chunkBase64: encodeBase64(normalizedChunk),
|
|
3975
4000
|
format: input.format,
|
|
@@ -4191,19 +4216,21 @@ var createVoiceSession = (options) => {
|
|
|
4191
4216
|
});
|
|
4192
4217
|
};
|
|
4193
4218
|
const transferInternal = async (input) => {
|
|
4219
|
+
const transferMetadata = input.transferMode === undefined ? input.metadata : { ...input.metadata ?? {}, transferMode: input.transferMode };
|
|
4194
4220
|
const session = await writeSession((currentSession) => {
|
|
4195
4221
|
pushCallLifecycleEvent(currentSession, {
|
|
4196
|
-
metadata:
|
|
4222
|
+
metadata: transferMetadata,
|
|
4197
4223
|
reason: input.reason,
|
|
4198
4224
|
target: input.target,
|
|
4199
4225
|
type: "transfer"
|
|
4200
4226
|
});
|
|
4201
4227
|
});
|
|
4202
4228
|
await appendTrace({
|
|
4203
|
-
metadata:
|
|
4229
|
+
metadata: transferMetadata,
|
|
4204
4230
|
payload: {
|
|
4205
4231
|
reason: input.reason,
|
|
4206
4232
|
target: input.target,
|
|
4233
|
+
transferMode: input.transferMode,
|
|
4207
4234
|
type: "transfer"
|
|
4208
4235
|
},
|
|
4209
4236
|
session,
|
|
@@ -5234,6 +5261,7 @@ var createVoiceSession = (options) => {
|
|
|
5234
5261
|
resumePendingTurnCommit(session);
|
|
5235
5262
|
await ensureAdapter();
|
|
5236
5263
|
warmTTSSession();
|
|
5264
|
+
kickCallSilenceWatchdog();
|
|
5237
5265
|
};
|
|
5238
5266
|
const disconnectInternal = async (event) => {
|
|
5239
5267
|
clearSilenceTimer();
|
|
@@ -5287,6 +5315,7 @@ var createVoiceSession = (options) => {
|
|
|
5287
5315
|
}
|
|
5288
5316
|
speechDetected = true;
|
|
5289
5317
|
clearSilenceTimer();
|
|
5318
|
+
kickCallSilenceWatchdog();
|
|
5290
5319
|
} else if (speechDetected) {
|
|
5291
5320
|
const currentSession = await readSession();
|
|
5292
5321
|
const hasTurnText = Boolean(buildTurnText(currentSession.currentTurn.transcripts, currentSession.currentTurn.partialText, {
|
|
@@ -5299,44 +5328,49 @@ var createVoiceSession = (options) => {
|
|
|
5299
5328
|
}
|
|
5300
5329
|
await adapter.send(conditionedAudio);
|
|
5301
5330
|
};
|
|
5331
|
+
const closeInternal = async (reason, disposition = "closed") => {
|
|
5332
|
+
const session = await writeSession((currentSession) => {
|
|
5333
|
+
if (currentSession.status !== "completed" && currentSession.status !== "failed" && !currentSession.call?.endedAt) {
|
|
5334
|
+
currentSession.lastActivityAt = Date.now();
|
|
5335
|
+
currentSession.status = "completed";
|
|
5336
|
+
pushCallLifecycleEvent(currentSession, {
|
|
5337
|
+
disposition,
|
|
5338
|
+
reason,
|
|
5339
|
+
type: "end"
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
});
|
|
5343
|
+
clearSilenceTimer();
|
|
5344
|
+
clearCallSilenceWatchdog();
|
|
5345
|
+
await closeTTSSession(reason);
|
|
5346
|
+
await closeAdapter(reason);
|
|
5347
|
+
await persistRecordings();
|
|
5348
|
+
await Promise.resolve(socket.close(1000, reason));
|
|
5349
|
+
if (session.call?.endedAt && session.call.disposition === disposition) {
|
|
5350
|
+
await appendTrace({
|
|
5351
|
+
payload: {
|
|
5352
|
+
disposition,
|
|
5353
|
+
reason,
|
|
5354
|
+
type: "end"
|
|
5355
|
+
},
|
|
5356
|
+
session,
|
|
5357
|
+
type: "call.lifecycle"
|
|
5358
|
+
});
|
|
5359
|
+
await options.route.onCallEnd?.({
|
|
5360
|
+
api,
|
|
5361
|
+
context: options.context,
|
|
5362
|
+
disposition,
|
|
5363
|
+
reason,
|
|
5364
|
+
session
|
|
5365
|
+
});
|
|
5366
|
+
}
|
|
5367
|
+
};
|
|
5302
5368
|
const api = {
|
|
5303
5369
|
id: options.id,
|
|
5304
5370
|
close: async (reason) => {
|
|
5305
5371
|
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
|
-
}
|
|
5372
|
+
const disposition = reason === "silence-timeout" ? "silence-timeout" : "closed";
|
|
5373
|
+
await closeInternal(reason, disposition);
|
|
5340
5374
|
});
|
|
5341
5375
|
},
|
|
5342
5376
|
commitTurn: async (reason = "manual") => runSerial("api.commitTurn", async () => {
|
|
@@ -9082,6 +9116,18 @@ var RECIPE_DEFAULTS = {
|
|
|
9082
9116
|
defaultQueue: "transfer-verification",
|
|
9083
9117
|
description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
|
|
9084
9118
|
escalationQueue: "transfer-escalations"
|
|
9119
|
+
},
|
|
9120
|
+
"cold-transfer": {
|
|
9121
|
+
completedAction: "Verify the SIP REFER landed and the caller reached the destination without re-introduction.",
|
|
9122
|
+
completedDescription: "The call was cold-transferred (SIP REFER) \u2014 confirm the destination picked up.",
|
|
9123
|
+
completedKind: "transfer-check",
|
|
9124
|
+
completedTitle: "Verify cold transfer",
|
|
9125
|
+
defaultCompletedCreatesTask: false,
|
|
9126
|
+
defaultDueInMs: 5 * 60000,
|
|
9127
|
+
defaultPriority: "normal",
|
|
9128
|
+
defaultQueue: "transfer-verification",
|
|
9129
|
+
description: "Creates verification work for cold-transferred (REFER) calls and escalation work when the handoff fails.",
|
|
9130
|
+
escalationQueue: "transfer-escalations"
|
|
9085
9131
|
}
|
|
9086
9132
|
};
|
|
9087
9133
|
var buildRecipeTask = (input) => {
|
|
@@ -9211,7 +9257,7 @@ var resolveVoiceOutcomeRecipe = (name, options = {}) => {
|
|
|
9211
9257
|
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
|
|
9212
9258
|
name: `${name}-transfer-check`,
|
|
9213
9259
|
priority: options.priority ?? defaults.defaultPriority,
|
|
9214
|
-
queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
9260
|
+
queue: name === "warm-transfer" || name === "cold-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
9215
9261
|
},
|
|
9216
9262
|
voicemail: {
|
|
9217
9263
|
assignee: options.assignee,
|
|
@@ -34999,7 +35045,8 @@ ${destinationDocs}`,
|
|
|
34999
35045
|
metadata: destination.metadata,
|
|
35000
35046
|
reason: args?.reason,
|
|
35001
35047
|
result,
|
|
35002
|
-
target: destination.target
|
|
35048
|
+
target: destination.target,
|
|
35049
|
+
transferMode: destination.transferMode
|
|
35003
35050
|
});
|
|
35004
35051
|
return {
|
|
35005
35052
|
destinationId: destination.id,
|
|
@@ -41255,6 +41302,62 @@ var createVoiceS3ReviewStore = (options) => {
|
|
|
41255
41302
|
set
|
|
41256
41303
|
};
|
|
41257
41304
|
};
|
|
41305
|
+
var normalizeRecordingKeyPrefix = (prefix) => prefix?.trim().replace(/^\/+|\/+$/g, "") ?? "voice/recordings";
|
|
41306
|
+
var recordingWavKey = (prefix, sessionId, channel) => `${prefix}/${encodeURIComponent(sessionId)}_${channel}.wav`;
|
|
41307
|
+
var recordingMetadataKey = (prefix, sessionId, channel) => `${prefix}/${encodeURIComponent(sessionId)}_${channel}.json`;
|
|
41308
|
+
var createVoiceS3RecordingStore = (options) => {
|
|
41309
|
+
const client = options.client ?? new Bun.S3Client(options);
|
|
41310
|
+
const keyPrefix = normalizeRecordingKeyPrefix(options.keyPrefix);
|
|
41311
|
+
const publicUrlBase = options.publicUrlBase?.replace(/\/+$/, "");
|
|
41312
|
+
const getFile = (key) => client.file(key, options);
|
|
41313
|
+
const resolveUrl = (key) => publicUrlBase ? `${publicUrlBase}/${key}` : `s3://${key}`;
|
|
41314
|
+
const put = async (artifact) => {
|
|
41315
|
+
const wavKey = recordingWavKey(keyPrefix, artifact.sessionId, artifact.channel);
|
|
41316
|
+
const metadataKey = recordingMetadataKey(keyPrefix, artifact.sessionId, artifact.channel);
|
|
41317
|
+
const wav = encodePcmAsWav(artifact.audioBytes, artifact.format);
|
|
41318
|
+
await getFile(wavKey).write(wav);
|
|
41319
|
+
const recordingUrl = resolveUrl(wavKey);
|
|
41320
|
+
const metadata = {
|
|
41321
|
+
capturedAt: artifact.capturedAt,
|
|
41322
|
+
channel: artifact.channel,
|
|
41323
|
+
durationMs: artifact.durationMs,
|
|
41324
|
+
format: artifact.format,
|
|
41325
|
+
recordingUrl,
|
|
41326
|
+
sessionId: artifact.sessionId
|
|
41327
|
+
};
|
|
41328
|
+
await getFile(metadataKey).write(JSON.stringify(metadata));
|
|
41329
|
+
return {
|
|
41330
|
+
...artifact,
|
|
41331
|
+
recordingUrl
|
|
41332
|
+
};
|
|
41333
|
+
};
|
|
41334
|
+
const readMetadata = async (sessionId, channel) => {
|
|
41335
|
+
const metadataKey = recordingMetadataKey(keyPrefix, sessionId, channel);
|
|
41336
|
+
const wavKey = recordingWavKey(keyPrefix, sessionId, channel);
|
|
41337
|
+
const metadataFile = getFile(metadataKey);
|
|
41338
|
+
if (!await metadataFile.exists()) {
|
|
41339
|
+
return;
|
|
41340
|
+
}
|
|
41341
|
+
const meta = JSON.parse(await metadataFile.text());
|
|
41342
|
+
const wavBytes = await getFile(wavKey).bytes();
|
|
41343
|
+
return {
|
|
41344
|
+
audioBytes: wavBytes,
|
|
41345
|
+
capturedAt: meta.capturedAt,
|
|
41346
|
+
channel: meta.channel,
|
|
41347
|
+
durationMs: meta.durationMs,
|
|
41348
|
+
format: meta.format,
|
|
41349
|
+
recordingUrl: meta.recordingUrl,
|
|
41350
|
+
sessionId: meta.sessionId
|
|
41351
|
+
};
|
|
41352
|
+
};
|
|
41353
|
+
const get = (sessionId, channel) => readMetadata(sessionId, channel);
|
|
41354
|
+
const list = async (sessionId) => {
|
|
41355
|
+
const channels = ["assistant", "user"];
|
|
41356
|
+
const records = await Promise.all(channels.map((channel) => readMetadata(sessionId, channel)));
|
|
41357
|
+
return records.filter((record) => record !== undefined);
|
|
41358
|
+
};
|
|
41359
|
+
return { get, list, put };
|
|
41360
|
+
};
|
|
41258
41361
|
// src/memoryStore.ts
|
|
41259
41362
|
var createVoiceMemoryStore = () => {
|
|
41260
41363
|
const sessions = new Map;
|
|
@@ -45687,6 +45790,7 @@ export {
|
|
|
45687
45790
|
createVoiceSQLiteAuditSinkDeliveryStore,
|
|
45688
45791
|
createVoiceSQLiteAuditEventStore,
|
|
45689
45792
|
createVoiceS3ReviewStore,
|
|
45793
|
+
createVoiceS3RecordingStore,
|
|
45690
45794
|
createVoiceS3DeliverySink,
|
|
45691
45795
|
createVoiceRoutingDecisionSummary,
|
|
45692
45796
|
createVoiceReviewSavedEvent,
|
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,30 @@ var createVoiceSession = (options) => {
|
|
|
5650
5650
|
const currentTurnAudio = [];
|
|
5651
5651
|
let fallbackAttemptsForCurrentTurn = 0;
|
|
5652
5652
|
let fallbackReplayAudioMsForCurrentTurn = 0;
|
|
5653
|
+
const callSilenceTimeoutMs = options.callSilenceTimeoutMs && options.callSilenceTimeoutMs > 0 ? options.callSilenceTimeoutMs : undefined;
|
|
5654
|
+
let callSilenceWatchdog = null;
|
|
5655
|
+
let callSilenceFired = false;
|
|
5656
|
+
const clearCallSilenceWatchdog = () => {
|
|
5657
|
+
if (callSilenceWatchdog) {
|
|
5658
|
+
clearTimeout(callSilenceWatchdog);
|
|
5659
|
+
callSilenceWatchdog = null;
|
|
5660
|
+
}
|
|
5661
|
+
};
|
|
5662
|
+
const fireCallSilenceTimeout = () => {
|
|
5663
|
+
callSilenceWatchdog = null;
|
|
5664
|
+
if (callSilenceFired) {
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
5667
|
+
callSilenceFired = true;
|
|
5668
|
+
api.close("silence-timeout");
|
|
5669
|
+
};
|
|
5670
|
+
const kickCallSilenceWatchdog = () => {
|
|
5671
|
+
if (callSilenceTimeoutMs === undefined || callSilenceFired) {
|
|
5672
|
+
return;
|
|
5673
|
+
}
|
|
5674
|
+
clearCallSilenceWatchdog();
|
|
5675
|
+
callSilenceWatchdog = setTimeout(fireCallSilenceTimeout, callSilenceTimeoutMs);
|
|
5676
|
+
};
|
|
5653
5677
|
const recordingConfig = options.recording;
|
|
5654
5678
|
const recordingChannels = new Set(recordingConfig?.channels ?? ["assistant", "user"]);
|
|
5655
5679
|
const recordingMaxBytes = recordingConfig?.maxBytesPerChannel ?? 50 * 1024 * 1024;
|
|
@@ -5938,6 +5962,7 @@ var createVoiceSession = (options) => {
|
|
|
5938
5962
|
const sendAssistantAudio = async (chunk, input) => {
|
|
5939
5963
|
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
5964
|
captureRecordingChunk("assistant", normalizedChunk, input.format);
|
|
5965
|
+
kickCallSilenceWatchdog();
|
|
5941
5966
|
await send({
|
|
5942
5967
|
chunkBase64: encodeBase64(normalizedChunk),
|
|
5943
5968
|
format: input.format,
|
|
@@ -6159,19 +6184,21 @@ var createVoiceSession = (options) => {
|
|
|
6159
6184
|
});
|
|
6160
6185
|
};
|
|
6161
6186
|
const transferInternal = async (input) => {
|
|
6187
|
+
const transferMetadata = input.transferMode === undefined ? input.metadata : { ...input.metadata ?? {}, transferMode: input.transferMode };
|
|
6162
6188
|
const session = await writeSession((currentSession) => {
|
|
6163
6189
|
pushCallLifecycleEvent(currentSession, {
|
|
6164
|
-
metadata:
|
|
6190
|
+
metadata: transferMetadata,
|
|
6165
6191
|
reason: input.reason,
|
|
6166
6192
|
target: input.target,
|
|
6167
6193
|
type: "transfer"
|
|
6168
6194
|
});
|
|
6169
6195
|
});
|
|
6170
6196
|
await appendTrace({
|
|
6171
|
-
metadata:
|
|
6197
|
+
metadata: transferMetadata,
|
|
6172
6198
|
payload: {
|
|
6173
6199
|
reason: input.reason,
|
|
6174
6200
|
target: input.target,
|
|
6201
|
+
transferMode: input.transferMode,
|
|
6175
6202
|
type: "transfer"
|
|
6176
6203
|
},
|
|
6177
6204
|
session,
|
|
@@ -7202,6 +7229,7 @@ var createVoiceSession = (options) => {
|
|
|
7202
7229
|
resumePendingTurnCommit(session);
|
|
7203
7230
|
await ensureAdapter();
|
|
7204
7231
|
warmTTSSession();
|
|
7232
|
+
kickCallSilenceWatchdog();
|
|
7205
7233
|
};
|
|
7206
7234
|
const disconnectInternal = async (event) => {
|
|
7207
7235
|
clearSilenceTimer();
|
|
@@ -7255,6 +7283,7 @@ var createVoiceSession = (options) => {
|
|
|
7255
7283
|
}
|
|
7256
7284
|
speechDetected = true;
|
|
7257
7285
|
clearSilenceTimer();
|
|
7286
|
+
kickCallSilenceWatchdog();
|
|
7258
7287
|
} else if (speechDetected) {
|
|
7259
7288
|
const currentSession = await readSession();
|
|
7260
7289
|
const hasTurnText = Boolean(buildTurnText(currentSession.currentTurn.transcripts, currentSession.currentTurn.partialText, {
|
|
@@ -7267,44 +7296,49 @@ var createVoiceSession = (options) => {
|
|
|
7267
7296
|
}
|
|
7268
7297
|
await adapter.send(conditionedAudio);
|
|
7269
7298
|
};
|
|
7299
|
+
const closeInternal = async (reason, disposition = "closed") => {
|
|
7300
|
+
const session = await writeSession((currentSession) => {
|
|
7301
|
+
if (currentSession.status !== "completed" && currentSession.status !== "failed" && !currentSession.call?.endedAt) {
|
|
7302
|
+
currentSession.lastActivityAt = Date.now();
|
|
7303
|
+
currentSession.status = "completed";
|
|
7304
|
+
pushCallLifecycleEvent(currentSession, {
|
|
7305
|
+
disposition,
|
|
7306
|
+
reason,
|
|
7307
|
+
type: "end"
|
|
7308
|
+
});
|
|
7309
|
+
}
|
|
7310
|
+
});
|
|
7311
|
+
clearSilenceTimer();
|
|
7312
|
+
clearCallSilenceWatchdog();
|
|
7313
|
+
await closeTTSSession(reason);
|
|
7314
|
+
await closeAdapter(reason);
|
|
7315
|
+
await persistRecordings();
|
|
7316
|
+
await Promise.resolve(socket.close(1000, reason));
|
|
7317
|
+
if (session.call?.endedAt && session.call.disposition === disposition) {
|
|
7318
|
+
await appendTrace({
|
|
7319
|
+
payload: {
|
|
7320
|
+
disposition,
|
|
7321
|
+
reason,
|
|
7322
|
+
type: "end"
|
|
7323
|
+
},
|
|
7324
|
+
session,
|
|
7325
|
+
type: "call.lifecycle"
|
|
7326
|
+
});
|
|
7327
|
+
await options.route.onCallEnd?.({
|
|
7328
|
+
api,
|
|
7329
|
+
context: options.context,
|
|
7330
|
+
disposition,
|
|
7331
|
+
reason,
|
|
7332
|
+
session
|
|
7333
|
+
});
|
|
7334
|
+
}
|
|
7335
|
+
};
|
|
7270
7336
|
const api = {
|
|
7271
7337
|
id: options.id,
|
|
7272
7338
|
close: async (reason) => {
|
|
7273
7339
|
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
|
-
}
|
|
7340
|
+
const disposition = reason === "silence-timeout" ? "silence-timeout" : "closed";
|
|
7341
|
+
await closeInternal(reason, disposition);
|
|
7308
7342
|
});
|
|
7309
7343
|
},
|
|
7310
7344
|
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,7 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
|
|
|
722
724
|
store: VoiceSessionStore<TSession>;
|
|
723
725
|
trace?: VoiceTraceEventStore;
|
|
724
726
|
recording?: VoiceSessionRecordingConfig;
|
|
727
|
+
callSilenceTimeoutMs?: number;
|
|
725
728
|
reconnect: Required<VoiceReconnectConfig>;
|
|
726
729
|
phraseHints?: VoicePhraseHint[];
|
|
727
730
|
sessionMetadata?: Record<string, unknown>;
|