@absolutejs/voice 0.0.22-beta.581 → 0.0.22-beta.583
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/client/audioPlayer.d.ts +7 -0
- package/dist/client/htmxBootstrap.js +25 -0
- package/dist/client/index.js +25 -0
- package/dist/core/debugTiming.d.ts +11 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +49 -1
- package/dist/testing/index.js +59 -1
- package/package.json +1 -1
|
@@ -22,9 +22,16 @@ type MinimalGainNode = {
|
|
|
22
22
|
value: number;
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
|
+
type MinimalAnalyserNode = {
|
|
26
|
+
connect?: (destination: unknown) => void;
|
|
27
|
+
disconnect?: () => void;
|
|
28
|
+
fftSize: number;
|
|
29
|
+
getByteTimeDomainData: (array: Uint8Array) => void;
|
|
30
|
+
};
|
|
25
31
|
type MinimalAudioContext = {
|
|
26
32
|
baseLatency?: number;
|
|
27
33
|
close: () => Promise<void>;
|
|
34
|
+
createAnalyser?: () => MinimalAnalyserNode;
|
|
28
35
|
createBuffer: (numberOfChannels: number, length: number, sampleRate: number) => MinimalAudioBuffer;
|
|
29
36
|
createBufferSource: () => MinimalAudioBufferSourceNode;
|
|
30
37
|
createGain?: () => MinimalGainNode;
|
|
@@ -1693,6 +1693,8 @@ var DEFAULT_PLAYBACK_RATE = 1;
|
|
|
1693
1693
|
var MIN_PLAYBACK_RATE = 0.5;
|
|
1694
1694
|
var MAX_PLAYBACK_RATE = 2;
|
|
1695
1695
|
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
1696
|
+
var ANALYSER_FFT_SIZE = 256;
|
|
1697
|
+
var PCM_BYTE_MIDPOINT = 128;
|
|
1696
1698
|
var createInitialState3 = () => ({
|
|
1697
1699
|
activeSourceCount: 0,
|
|
1698
1700
|
error: null,
|
|
@@ -1753,6 +1755,8 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1753
1755
|
let state = createInitialState3();
|
|
1754
1756
|
let audioContext = null;
|
|
1755
1757
|
let outputNode = null;
|
|
1758
|
+
let analyserNode = null;
|
|
1759
|
+
let analyserBuffer = null;
|
|
1756
1760
|
let volume = clampVolume(options.volume);
|
|
1757
1761
|
let playbackRate = clampPlaybackRate(options.playbackRate);
|
|
1758
1762
|
let stretcher = null;
|
|
@@ -1849,6 +1853,12 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1849
1853
|
if (audioContext.createGain) {
|
|
1850
1854
|
outputNode = audioContext.createGain();
|
|
1851
1855
|
outputNode.connect?.(audioContext.destination);
|
|
1856
|
+
if (audioContext.createAnalyser) {
|
|
1857
|
+
analyserNode = audioContext.createAnalyser();
|
|
1858
|
+
analyserNode.fftSize = ANALYSER_FFT_SIZE;
|
|
1859
|
+
analyserBuffer = new Uint8Array(analyserNode.fftSize);
|
|
1860
|
+
outputNode.connect?.(analyserNode);
|
|
1861
|
+
}
|
|
1852
1862
|
}
|
|
1853
1863
|
queueEndTime = audioContext.currentTime;
|
|
1854
1864
|
return audioContext;
|
|
@@ -1973,6 +1983,9 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1973
1983
|
audioContext = null;
|
|
1974
1984
|
outputNode?.disconnect?.();
|
|
1975
1985
|
outputNode = null;
|
|
1986
|
+
analyserNode?.disconnect?.();
|
|
1987
|
+
analyserNode = null;
|
|
1988
|
+
analyserBuffer = null;
|
|
1976
1989
|
queueEndTime = 0;
|
|
1977
1990
|
setState({
|
|
1978
1991
|
activeSourceCount: 0,
|
|
@@ -1983,6 +1996,18 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1983
1996
|
get error() {
|
|
1984
1997
|
return state.error;
|
|
1985
1998
|
},
|
|
1999
|
+
getOutputLevel: () => {
|
|
2000
|
+
if (!analyserNode || !analyserBuffer) {
|
|
2001
|
+
return 0;
|
|
2002
|
+
}
|
|
2003
|
+
analyserNode.getByteTimeDomainData(analyserBuffer);
|
|
2004
|
+
let sumSquares = 0;
|
|
2005
|
+
for (const sample of analyserBuffer) {
|
|
2006
|
+
const centered = (sample - PCM_BYTE_MIDPOINT) / PCM_BYTE_MIDPOINT;
|
|
2007
|
+
sumSquares += centered * centered;
|
|
2008
|
+
}
|
|
2009
|
+
return Math.sqrt(sumSquares / analyserBuffer.length);
|
|
2010
|
+
},
|
|
1986
2011
|
getSnapshot: () => state,
|
|
1987
2012
|
interrupt: async () => {
|
|
1988
2013
|
const startedAt = Date.now();
|
package/dist/client/index.js
CHANGED
|
@@ -529,6 +529,8 @@ var DEFAULT_PLAYBACK_RATE = 1;
|
|
|
529
529
|
var MIN_PLAYBACK_RATE = 0.5;
|
|
530
530
|
var MAX_PLAYBACK_RATE = 2;
|
|
531
531
|
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
532
|
+
var ANALYSER_FFT_SIZE = 256;
|
|
533
|
+
var PCM_BYTE_MIDPOINT = 128;
|
|
532
534
|
var createInitialState = () => ({
|
|
533
535
|
activeSourceCount: 0,
|
|
534
536
|
error: null,
|
|
@@ -589,6 +591,8 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
589
591
|
let state = createInitialState();
|
|
590
592
|
let audioContext = null;
|
|
591
593
|
let outputNode = null;
|
|
594
|
+
let analyserNode = null;
|
|
595
|
+
let analyserBuffer = null;
|
|
592
596
|
let volume = clampVolume(options.volume);
|
|
593
597
|
let playbackRate = clampPlaybackRate(options.playbackRate);
|
|
594
598
|
let stretcher = null;
|
|
@@ -685,6 +689,12 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
685
689
|
if (audioContext.createGain) {
|
|
686
690
|
outputNode = audioContext.createGain();
|
|
687
691
|
outputNode.connect?.(audioContext.destination);
|
|
692
|
+
if (audioContext.createAnalyser) {
|
|
693
|
+
analyserNode = audioContext.createAnalyser();
|
|
694
|
+
analyserNode.fftSize = ANALYSER_FFT_SIZE;
|
|
695
|
+
analyserBuffer = new Uint8Array(analyserNode.fftSize);
|
|
696
|
+
outputNode.connect?.(analyserNode);
|
|
697
|
+
}
|
|
688
698
|
}
|
|
689
699
|
queueEndTime = audioContext.currentTime;
|
|
690
700
|
return audioContext;
|
|
@@ -809,6 +819,9 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
809
819
|
audioContext = null;
|
|
810
820
|
outputNode?.disconnect?.();
|
|
811
821
|
outputNode = null;
|
|
822
|
+
analyserNode?.disconnect?.();
|
|
823
|
+
analyserNode = null;
|
|
824
|
+
analyserBuffer = null;
|
|
812
825
|
queueEndTime = 0;
|
|
813
826
|
setState({
|
|
814
827
|
activeSourceCount: 0,
|
|
@@ -819,6 +832,18 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
819
832
|
get error() {
|
|
820
833
|
return state.error;
|
|
821
834
|
},
|
|
835
|
+
getOutputLevel: () => {
|
|
836
|
+
if (!analyserNode || !analyserBuffer) {
|
|
837
|
+
return 0;
|
|
838
|
+
}
|
|
839
|
+
analyserNode.getByteTimeDomainData(analyserBuffer);
|
|
840
|
+
let sumSquares = 0;
|
|
841
|
+
for (const sample of analyserBuffer) {
|
|
842
|
+
const centered = (sample - PCM_BYTE_MIDPOINT) / PCM_BYTE_MIDPOINT;
|
|
843
|
+
sumSquares += centered * centered;
|
|
844
|
+
}
|
|
845
|
+
return Math.sqrt(sumSquares / analyserBuffer.length);
|
|
846
|
+
},
|
|
822
847
|
getSnapshot: () => state,
|
|
823
848
|
interrupt: async () => {
|
|
824
849
|
const startedAt = Date.now();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const logVoiceTiming: (sessionId: string, stage: string, elapsedMs: number, detail?: Record<string, unknown>) => void;
|
|
2
|
+
/**
|
|
3
|
+
* A per-turn stopwatch. `stamp(stage, detail?)` logs the elapsed since the timer
|
|
4
|
+
* was created, so one timer per turn lays every stage out on a single timeline:
|
|
5
|
+
*
|
|
6
|
+
* const stamp = startVoiceTimer(session.id);
|
|
7
|
+
* stamp("agent.system-resolved", { chars });
|
|
8
|
+
* stamp("agent.round0.generate-done", { ms });
|
|
9
|
+
*/
|
|
10
|
+
export declare const startVoiceTimer: (sessionId: string) => (stage: string, detail?: Record<string, unknown>) => void;
|
|
11
|
+
export declare const voiceTimingEnabled: () => boolean;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1331,6 +1331,9 @@ export type VoiceAudioPlayerSource = {
|
|
|
1331
1331
|
export type VoiceAudioPlayer = {
|
|
1332
1332
|
close: () => Promise<void>;
|
|
1333
1333
|
error: string | null;
|
|
1334
|
+
/** Instantaneous RMS amplitude (0..1) of the assistant's audio output — for
|
|
1335
|
+
* driving a visualizer from the actual voice. 0 when idle / no analyser. */
|
|
1336
|
+
getOutputLevel: () => number;
|
|
1334
1337
|
getSnapshot: () => VoiceAudioPlayerState;
|
|
1335
1338
|
activeSourceCount: number;
|
|
1336
1339
|
isActive: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,7 @@ export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, crea
|
|
|
71
71
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./core/agent";
|
|
72
72
|
export { createPersonaVoiceCaller, createScriptedVoiceCaller, renderVoiceSimulationTranscript, runVoiceConversationSimulation, } from "./core/conversationSimulator";
|
|
73
73
|
export type { RunVoiceConversationSimulationInput, VoiceConversationSimulationEndedReason, VoiceConversationSimulationResult, VoicePersonaCallerCompletion, VoiceScriptedCallerStep, VoiceSimulatedSpeaker, VoiceSimulatedTurn, VoiceSimulatorCaller, VoiceSimulatorCallerModel, VoiceSimulatorCallerReply, } from "./core/conversationSimulator";
|
|
74
|
+
export { logVoiceTiming, startVoiceTimer, voiceTimingEnabled, } from "./core/debugTiming";
|
|
74
75
|
export { createVoiceMCPToolset } from "./core/mcpToolset";
|
|
75
76
|
export type { CreateVoiceMCPToolsetOptions, MCPClientLike, MCPToolCallResult, MCPToolContentBlock, MCPToolDefinition, VoiceMCPToolResult, } from "./core/mcpToolset";
|
|
76
77
|
export { createAIVoiceModel } from "./core/aiVoiceModel";
|
package/dist/index.js
CHANGED
|
@@ -3091,6 +3091,21 @@ var toVoiceSessionSummary = (session) => ({
|
|
|
3091
3091
|
// src/core/session.ts
|
|
3092
3092
|
import { Buffer as Buffer2 } from "buffer";
|
|
3093
3093
|
|
|
3094
|
+
// src/core/debugTiming.ts
|
|
3095
|
+
var timingEnabled = () => process.env.ABSOLUTEJS_VOICE_TIMING === "1" || process.env.ABSOLUTEJS_VOICE_TIMING === "true";
|
|
3096
|
+
var emitTiming = (sessionId, stage, elapsedMs, detail) => {
|
|
3097
|
+
if (!timingEnabled())
|
|
3098
|
+
return;
|
|
3099
|
+
const extra = detail ? ` ${JSON.stringify(detail)}` : "";
|
|
3100
|
+
console.info(`[voice][timing] session=${sessionId} ${stage} +${Math.round(elapsedMs)}ms${extra}`);
|
|
3101
|
+
};
|
|
3102
|
+
var logVoiceTiming = (sessionId, stage, elapsedMs, detail) => emitTiming(sessionId, stage, elapsedMs, detail);
|
|
3103
|
+
var startVoiceTimer = (sessionId) => {
|
|
3104
|
+
const startedAt = Date.now();
|
|
3105
|
+
return (stage, detail) => emitTiming(sessionId, stage, Date.now() - startedAt, detail);
|
|
3106
|
+
};
|
|
3107
|
+
var voiceTimingEnabled = () => timingEnabled();
|
|
3108
|
+
|
|
3094
3109
|
// src/core/backchannel.ts
|
|
3095
3110
|
var DEFAULT_CUES = [
|
|
3096
3111
|
{ text: "mm-hmm" },
|
|
@@ -5534,6 +5549,7 @@ var createVoiceSession = (options) => {
|
|
|
5534
5549
|
const onTurnTimeoutMs = options.routeOnTurnTimeoutMs ?? 45000;
|
|
5535
5550
|
let committedOutput;
|
|
5536
5551
|
const onTurnStartedAt = Date.now();
|
|
5552
|
+
logVoiceTiming(session.id, "session.commit-to-onturn", onTurnStartedAt - (turn.committedAt || onTurnStartedAt), { fillerScheduled: fillerTimer !== null });
|
|
5537
5553
|
try {
|
|
5538
5554
|
const onTurnPromise = options.route.onTurn({
|
|
5539
5555
|
api,
|
|
@@ -7446,7 +7462,9 @@ var createVoiceAgent = (options) => {
|
|
|
7446
7462
|
const maxToolRounds = Math.max(0, options.maxToolRounds ?? 2);
|
|
7447
7463
|
const audit = resolveVoiceAgentAuditLogger(options.audit);
|
|
7448
7464
|
const run = async (input) => {
|
|
7465
|
+
const stamp = startVoiceTimer(input.session.id);
|
|
7449
7466
|
const messages = input.messages ?? createHistoryMessages(input.session, input.turn);
|
|
7467
|
+
stamp("agent.history-built", { messages: messages.length });
|
|
7450
7468
|
const toolResults = [];
|
|
7451
7469
|
const baseSystem = typeof options.system === "function" ? await options.system({
|
|
7452
7470
|
context: input.context,
|
|
@@ -7456,9 +7474,11 @@ var createVoiceAgent = (options) => {
|
|
|
7456
7474
|
const system = [baseSystem, input.system].filter((value) => Boolean(value?.trim())).join(`
|
|
7457
7475
|
|
|
7458
7476
|
`) || undefined;
|
|
7477
|
+
stamp("agent.system-resolved", { systemChars: system?.length ?? 0 });
|
|
7459
7478
|
let output = {};
|
|
7460
7479
|
for (let round = 0;round <= maxToolRounds; round += 1) {
|
|
7461
7480
|
const modelStartedAt = Date.now();
|
|
7481
|
+
stamp(`agent.round${round}.generate-start`);
|
|
7462
7482
|
try {
|
|
7463
7483
|
output = await options.model.generate({
|
|
7464
7484
|
agentId: options.id,
|
|
@@ -7474,6 +7494,11 @@ var createVoiceAgent = (options) => {
|
|
|
7474
7494
|
})),
|
|
7475
7495
|
turn: input.turn
|
|
7476
7496
|
});
|
|
7497
|
+
stamp(`agent.round${round}.generate-done`, {
|
|
7498
|
+
ms: Date.now() - modelStartedAt,
|
|
7499
|
+
textChars: output.assistantText?.length ?? 0,
|
|
7500
|
+
toolCalls: output.toolCalls?.length ?? 0
|
|
7501
|
+
});
|
|
7477
7502
|
await audit?.providerCall({
|
|
7478
7503
|
actor: {
|
|
7479
7504
|
id: options.id,
|
|
@@ -7487,6 +7512,9 @@ var createVoiceAgent = (options) => {
|
|
|
7487
7512
|
sessionId: input.session.id
|
|
7488
7513
|
});
|
|
7489
7514
|
} catch (error) {
|
|
7515
|
+
stamp(`agent.round${round}.generate-error`, {
|
|
7516
|
+
ms: Date.now() - modelStartedAt
|
|
7517
|
+
});
|
|
7490
7518
|
await audit?.providerCall({
|
|
7491
7519
|
actor: {
|
|
7492
7520
|
id: options.id,
|
|
@@ -45368,6 +45396,7 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
45368
45396
|
const timeoutMs = options.timeoutMs ?? 60000;
|
|
45369
45397
|
return {
|
|
45370
45398
|
generate: async (input) => {
|
|
45399
|
+
const stamp = startVoiceTimer(input.session.id);
|
|
45371
45400
|
const ac = new AbortController;
|
|
45372
45401
|
const timer = setTimeout(() => {
|
|
45373
45402
|
ac.abort(new Error(`OpenAI /responses timed out after ${timeoutMs}ms (no completion event received)`));
|
|
@@ -45408,6 +45437,10 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
45408
45437
|
clearTimeout(timer);
|
|
45409
45438
|
throw error;
|
|
45410
45439
|
}
|
|
45440
|
+
stamp("openai.fetch-returned", {
|
|
45441
|
+
messages: input.messages.length,
|
|
45442
|
+
status: response.status
|
|
45443
|
+
});
|
|
45411
45444
|
if (!response.ok) {
|
|
45412
45445
|
clearTimeout(timer);
|
|
45413
45446
|
throw createHTTPError("OpenAI", response);
|
|
@@ -45415,11 +45448,23 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
45415
45448
|
let assistantText;
|
|
45416
45449
|
let toolCalls;
|
|
45417
45450
|
let usage;
|
|
45451
|
+
let firstDeltaSeen = false;
|
|
45452
|
+
const onTextDelta = input.onTextDelta ? (delta) => {
|
|
45453
|
+
if (!firstDeltaSeen) {
|
|
45454
|
+
firstDeltaSeen = true;
|
|
45455
|
+
stamp("openai.first-delta");
|
|
45456
|
+
}
|
|
45457
|
+
input.onTextDelta?.(delta);
|
|
45458
|
+
} : undefined;
|
|
45418
45459
|
try {
|
|
45419
|
-
({ assistantText, toolCalls, usage } = await consumeOpenAIResponsesStream(response,
|
|
45460
|
+
({ assistantText, toolCalls, usage } = await consumeOpenAIResponsesStream(response, onTextDelta, {
|
|
45420
45461
|
signal: ac.signal,
|
|
45421
45462
|
inactivityMs: 1e4
|
|
45422
45463
|
}));
|
|
45464
|
+
stamp("openai.stream-done", {
|
|
45465
|
+
textChars: assistantText?.length ?? 0,
|
|
45466
|
+
toolCalls: toolCalls.length
|
|
45467
|
+
});
|
|
45423
45468
|
} finally {
|
|
45424
45469
|
clearTimeout(timer);
|
|
45425
45470
|
}
|
|
@@ -52391,6 +52436,7 @@ export {
|
|
|
52391
52436
|
wrapVoiceHTMLInHTMXContainer,
|
|
52392
52437
|
withVoiceOpsTaskId,
|
|
52393
52438
|
withVoiceIntegrationEventId,
|
|
52439
|
+
voiceTimingEnabled,
|
|
52394
52440
|
voiceTelephonyOutcomeToRouteResult,
|
|
52395
52441
|
voiceObservabilityExportSchemaVersion,
|
|
52396
52442
|
voiceObservabilityExportSchemaId,
|
|
@@ -52448,6 +52494,7 @@ export {
|
|
|
52448
52494
|
summarizeVoiceAuditSinkDeliveries,
|
|
52449
52495
|
summarizeVoiceAssistantRuns,
|
|
52450
52496
|
summarizeVoiceAssistantHealth,
|
|
52497
|
+
startVoiceTimer,
|
|
52451
52498
|
startVoiceOpsTask,
|
|
52452
52499
|
signVoiceWebhookBody,
|
|
52453
52500
|
signVoiceTwilioWebhook,
|
|
@@ -52648,6 +52695,7 @@ export {
|
|
|
52648
52695
|
matchesVoiceOpsTaskAssignmentRule,
|
|
52649
52696
|
markVoiceOpsTaskSLABreached,
|
|
52650
52697
|
mapVoiceProofTargetsWithConcurrency,
|
|
52698
|
+
logVoiceTiming,
|
|
52651
52699
|
loadVoiceRealCallProfileEvidenceFromTraceStore,
|
|
52652
52700
|
loadVoiceRealCallProfileEvidenceFromStore,
|
|
52653
52701
|
loadVoiceObservabilityExportReplaySource,
|
package/dist/testing/index.js
CHANGED
|
@@ -1736,6 +1736,8 @@ var DEFAULT_PLAYBACK_RATE = 1;
|
|
|
1736
1736
|
var MIN_PLAYBACK_RATE = 0.5;
|
|
1737
1737
|
var MAX_PLAYBACK_RATE = 2;
|
|
1738
1738
|
var STRETCH_BYPASS_EPSILON = 0.01;
|
|
1739
|
+
var ANALYSER_FFT_SIZE = 256;
|
|
1740
|
+
var PCM_BYTE_MIDPOINT = 128;
|
|
1739
1741
|
var createInitialState = () => ({
|
|
1740
1742
|
activeSourceCount: 0,
|
|
1741
1743
|
error: null,
|
|
@@ -1796,6 +1798,8 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1796
1798
|
let state = createInitialState();
|
|
1797
1799
|
let audioContext = null;
|
|
1798
1800
|
let outputNode = null;
|
|
1801
|
+
let analyserNode = null;
|
|
1802
|
+
let analyserBuffer = null;
|
|
1799
1803
|
let volume = clampVolume(options.volume);
|
|
1800
1804
|
let playbackRate = clampPlaybackRate(options.playbackRate);
|
|
1801
1805
|
let stretcher = null;
|
|
@@ -1892,6 +1896,12 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
1892
1896
|
if (audioContext.createGain) {
|
|
1893
1897
|
outputNode = audioContext.createGain();
|
|
1894
1898
|
outputNode.connect?.(audioContext.destination);
|
|
1899
|
+
if (audioContext.createAnalyser) {
|
|
1900
|
+
analyserNode = audioContext.createAnalyser();
|
|
1901
|
+
analyserNode.fftSize = ANALYSER_FFT_SIZE;
|
|
1902
|
+
analyserBuffer = new Uint8Array(analyserNode.fftSize);
|
|
1903
|
+
outputNode.connect?.(analyserNode);
|
|
1904
|
+
}
|
|
1895
1905
|
}
|
|
1896
1906
|
queueEndTime = audioContext.currentTime;
|
|
1897
1907
|
return audioContext;
|
|
@@ -2016,6 +2026,9 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2016
2026
|
audioContext = null;
|
|
2017
2027
|
outputNode?.disconnect?.();
|
|
2018
2028
|
outputNode = null;
|
|
2029
|
+
analyserNode?.disconnect?.();
|
|
2030
|
+
analyserNode = null;
|
|
2031
|
+
analyserBuffer = null;
|
|
2019
2032
|
queueEndTime = 0;
|
|
2020
2033
|
setState({
|
|
2021
2034
|
activeSourceCount: 0,
|
|
@@ -2026,6 +2039,18 @@ var createVoiceAudioPlayer = (source, options = {}) => {
|
|
|
2026
2039
|
get error() {
|
|
2027
2040
|
return state.error;
|
|
2028
2041
|
},
|
|
2042
|
+
getOutputLevel: () => {
|
|
2043
|
+
if (!analyserNode || !analyserBuffer) {
|
|
2044
|
+
return 0;
|
|
2045
|
+
}
|
|
2046
|
+
analyserNode.getByteTimeDomainData(analyserBuffer);
|
|
2047
|
+
let sumSquares = 0;
|
|
2048
|
+
for (const sample of analyserBuffer) {
|
|
2049
|
+
const centered = (sample - PCM_BYTE_MIDPOINT) / PCM_BYTE_MIDPOINT;
|
|
2050
|
+
sumSquares += centered * centered;
|
|
2051
|
+
}
|
|
2052
|
+
return Math.sqrt(sumSquares / analyserBuffer.length);
|
|
2053
|
+
},
|
|
2029
2054
|
getSnapshot: () => state,
|
|
2030
2055
|
interrupt: async () => {
|
|
2031
2056
|
const startedAt = Date.now();
|
|
@@ -4170,6 +4195,21 @@ var createVoiceIOProviderFailureSimulator = (options) => {
|
|
|
4170
4195
|
run
|
|
4171
4196
|
};
|
|
4172
4197
|
};
|
|
4198
|
+
// src/core/debugTiming.ts
|
|
4199
|
+
var timingEnabled = () => process.env.ABSOLUTEJS_VOICE_TIMING === "1" || process.env.ABSOLUTEJS_VOICE_TIMING === "true";
|
|
4200
|
+
var emitTiming = (sessionId, stage, elapsedMs, detail) => {
|
|
4201
|
+
if (!timingEnabled())
|
|
4202
|
+
return;
|
|
4203
|
+
const extra = detail ? ` ${JSON.stringify(detail)}` : "";
|
|
4204
|
+
console.info(`[voice][timing] session=${sessionId} ${stage} +${Math.round(elapsedMs)}ms${extra}`);
|
|
4205
|
+
};
|
|
4206
|
+
var logVoiceTiming = (sessionId, stage, elapsedMs, detail) => emitTiming(sessionId, stage, elapsedMs, detail);
|
|
4207
|
+
var startVoiceTimer = (sessionId) => {
|
|
4208
|
+
const startedAt = Date.now();
|
|
4209
|
+
return (stage, detail) => emitTiming(sessionId, stage, Date.now() - startedAt, detail);
|
|
4210
|
+
};
|
|
4211
|
+
var voiceTimingEnabled = () => timingEnabled();
|
|
4212
|
+
|
|
4173
4213
|
// src/core/modelAdapters.ts
|
|
4174
4214
|
var isVoiceProviderRoutingPolicyPreset = (value) => value === "balanced" || value === "cost-cap" || value === "cost-first" || value === "latency-first" || value === "quality-first";
|
|
4175
4215
|
var resolveVoiceProviderRoutingPolicyPreset = (preset, options = {}) => {
|
|
@@ -4880,6 +4920,7 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
4880
4920
|
const timeoutMs = options.timeoutMs ?? 60000;
|
|
4881
4921
|
return {
|
|
4882
4922
|
generate: async (input) => {
|
|
4923
|
+
const stamp = startVoiceTimer(input.session.id);
|
|
4883
4924
|
const ac = new AbortController;
|
|
4884
4925
|
const timer = setTimeout(() => {
|
|
4885
4926
|
ac.abort(new Error(`OpenAI /responses timed out after ${timeoutMs}ms (no completion event received)`));
|
|
@@ -4920,6 +4961,10 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
4920
4961
|
clearTimeout(timer);
|
|
4921
4962
|
throw error;
|
|
4922
4963
|
}
|
|
4964
|
+
stamp("openai.fetch-returned", {
|
|
4965
|
+
messages: input.messages.length,
|
|
4966
|
+
status: response.status
|
|
4967
|
+
});
|
|
4923
4968
|
if (!response.ok) {
|
|
4924
4969
|
clearTimeout(timer);
|
|
4925
4970
|
throw createHTTPError("OpenAI", response);
|
|
@@ -4927,11 +4972,23 @@ var createOpenAIVoiceAssistantModel = (options) => {
|
|
|
4927
4972
|
let assistantText;
|
|
4928
4973
|
let toolCalls;
|
|
4929
4974
|
let usage;
|
|
4975
|
+
let firstDeltaSeen = false;
|
|
4976
|
+
const onTextDelta = input.onTextDelta ? (delta) => {
|
|
4977
|
+
if (!firstDeltaSeen) {
|
|
4978
|
+
firstDeltaSeen = true;
|
|
4979
|
+
stamp("openai.first-delta");
|
|
4980
|
+
}
|
|
4981
|
+
input.onTextDelta?.(delta);
|
|
4982
|
+
} : undefined;
|
|
4930
4983
|
try {
|
|
4931
|
-
({ assistantText, toolCalls, usage } = await consumeOpenAIResponsesStream(response,
|
|
4984
|
+
({ assistantText, toolCalls, usage } = await consumeOpenAIResponsesStream(response, onTextDelta, {
|
|
4932
4985
|
signal: ac.signal,
|
|
4933
4986
|
inactivityMs: 1e4
|
|
4934
4987
|
}));
|
|
4988
|
+
stamp("openai.stream-done", {
|
|
4989
|
+
textChars: assistantText?.length ?? 0,
|
|
4990
|
+
toolCalls: toolCalls.length
|
|
4991
|
+
});
|
|
4935
4992
|
} finally {
|
|
4936
4993
|
clearTimeout(timer);
|
|
4937
4994
|
}
|
|
@@ -7651,6 +7708,7 @@ var createVoiceSession = (options) => {
|
|
|
7651
7708
|
const onTurnTimeoutMs = options.routeOnTurnTimeoutMs ?? 45000;
|
|
7652
7709
|
let committedOutput;
|
|
7653
7710
|
const onTurnStartedAt = Date.now();
|
|
7711
|
+
logVoiceTiming(session.id, "session.commit-to-onturn", onTurnStartedAt - (turn.committedAt || onTurnStartedAt), { fillerScheduled: fillerTimer !== null });
|
|
7654
7712
|
try {
|
|
7655
7713
|
const onTurnPromise = options.route.onTurn({
|
|
7656
7714
|
api,
|