@absolutejs/voice 0.0.22-beta.95 → 0.0.22-beta.97
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/index.js +112 -10
- package/dist/testing/index.js +57 -1
- package/dist/trace.d.ts +1 -1
- package/dist/turnLatency.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3596,7 +3596,7 @@ var createVoiceSession = (options) => {
|
|
|
3596
3596
|
} : undefined;
|
|
3597
3597
|
const appendTrace = async (input) => {
|
|
3598
3598
|
await options.trace?.append({
|
|
3599
|
-
at: Date.now(),
|
|
3599
|
+
at: input.at ?? Date.now(),
|
|
3600
3600
|
metadata: input.metadata,
|
|
3601
3601
|
payload: input.payload,
|
|
3602
3602
|
scenarioId: input.session?.scenarioId ?? options.scenarioId,
|
|
@@ -3605,6 +3605,13 @@ var createVoiceSession = (options) => {
|
|
|
3605
3605
|
type: input.type
|
|
3606
3606
|
});
|
|
3607
3607
|
};
|
|
3608
|
+
const appendTurnLatencyStage = async (input) => appendTrace({
|
|
3609
|
+
at: input.at,
|
|
3610
|
+
payload: { stage: input.stage },
|
|
3611
|
+
session: input.session,
|
|
3612
|
+
turnId: input.turnId,
|
|
3613
|
+
type: "turn_latency.stage"
|
|
3614
|
+
});
|
|
3608
3615
|
const phraseHints = options.phraseHints ?? [];
|
|
3609
3616
|
const lexicon = options.lexicon ?? [];
|
|
3610
3617
|
let socket = options.socket;
|
|
@@ -4555,6 +4562,13 @@ var createVoiceSession = (options) => {
|
|
|
4555
4562
|
turnId: activeTTSTurnId,
|
|
4556
4563
|
type: "audio"
|
|
4557
4564
|
});
|
|
4565
|
+
if (activeTTSTurnId) {
|
|
4566
|
+
await appendTurnLatencyStage({
|
|
4567
|
+
at: receivedAt,
|
|
4568
|
+
stage: "assistant_audio_received",
|
|
4569
|
+
turnId: activeTTSTurnId
|
|
4570
|
+
});
|
|
4571
|
+
}
|
|
4558
4572
|
});
|
|
4559
4573
|
});
|
|
4560
4574
|
openedSession.on("error", (event) => {
|
|
@@ -4613,6 +4627,7 @@ var createVoiceSession = (options) => {
|
|
|
4613
4627
|
voicemail: committedOutput?.voicemail
|
|
4614
4628
|
};
|
|
4615
4629
|
if (output?.assistantText) {
|
|
4630
|
+
const assistantTextStartedAt = Date.now();
|
|
4616
4631
|
await writeSession((currentSession) => {
|
|
4617
4632
|
setTurnResult(currentSession, turn.id, {
|
|
4618
4633
|
assistantText: output.assistantText
|
|
@@ -4623,6 +4638,12 @@ var createVoiceSession = (options) => {
|
|
|
4623
4638
|
turnId: turn.id,
|
|
4624
4639
|
type: "assistant"
|
|
4625
4640
|
});
|
|
4641
|
+
await appendTurnLatencyStage({
|
|
4642
|
+
at: assistantTextStartedAt,
|
|
4643
|
+
session,
|
|
4644
|
+
stage: "assistant_text_started",
|
|
4645
|
+
turnId: turn.id
|
|
4646
|
+
});
|
|
4626
4647
|
await appendTrace({
|
|
4627
4648
|
payload: {
|
|
4628
4649
|
text: output.assistantText,
|
|
@@ -4637,7 +4658,18 @@ var createVoiceSession = (options) => {
|
|
|
4637
4658
|
if (activeTTSSession) {
|
|
4638
4659
|
const ttsStartedAt = Date.now();
|
|
4639
4660
|
activeTTSTurnId = turn.id;
|
|
4661
|
+
await appendTurnLatencyStage({
|
|
4662
|
+
at: ttsStartedAt,
|
|
4663
|
+
session,
|
|
4664
|
+
stage: "tts_send_started",
|
|
4665
|
+
turnId: turn.id
|
|
4666
|
+
});
|
|
4640
4667
|
await activeTTSSession.send(output.assistantText);
|
|
4668
|
+
await appendTurnLatencyStage({
|
|
4669
|
+
session,
|
|
4670
|
+
stage: "tts_send_completed",
|
|
4671
|
+
turnId: turn.id
|
|
4672
|
+
});
|
|
4641
4673
|
await appendTrace({
|
|
4642
4674
|
payload: {
|
|
4643
4675
|
elapsedMs: Date.now() - ttsStartedAt,
|
|
@@ -4834,6 +4866,30 @@ var createVoiceSession = (options) => {
|
|
|
4834
4866
|
turnId: turn.id,
|
|
4835
4867
|
type: "turn.cost"
|
|
4836
4868
|
});
|
|
4869
|
+
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
4870
|
+
const finalTranscriptAt = turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
4871
|
+
if (firstTranscriptAt !== undefined) {
|
|
4872
|
+
await appendTurnLatencyStage({
|
|
4873
|
+
at: firstTranscriptAt,
|
|
4874
|
+
session: updatedSession,
|
|
4875
|
+
stage: "speech_detected",
|
|
4876
|
+
turnId: turn.id
|
|
4877
|
+
});
|
|
4878
|
+
}
|
|
4879
|
+
if (finalTranscriptAt !== undefined) {
|
|
4880
|
+
await appendTurnLatencyStage({
|
|
4881
|
+
at: finalTranscriptAt,
|
|
4882
|
+
session: updatedSession,
|
|
4883
|
+
stage: "final_transcript",
|
|
4884
|
+
turnId: turn.id
|
|
4885
|
+
});
|
|
4886
|
+
}
|
|
4887
|
+
await appendTurnLatencyStage({
|
|
4888
|
+
at: turn.committedAt,
|
|
4889
|
+
session: updatedSession,
|
|
4890
|
+
stage: "turn_committed",
|
|
4891
|
+
turnId: turn.id
|
|
4892
|
+
});
|
|
4837
4893
|
await send({
|
|
4838
4894
|
turn,
|
|
4839
4895
|
type: "turn"
|
|
@@ -9965,6 +10021,8 @@ var timelineLabel = (event) => {
|
|
|
9965
10021
|
return `Error${getString9(event.payload.error) ? `: ${getString9(event.payload.error)}` : ""}`;
|
|
9966
10022
|
case "turn.cost":
|
|
9967
10023
|
return "Cost telemetry";
|
|
10024
|
+
case "turn_latency.stage":
|
|
10025
|
+
return `Latency ${getString9(event.payload.stage) ?? "stage"}`;
|
|
9968
10026
|
case "workflow.contract":
|
|
9969
10027
|
return `Workflow contract ${eventStatus(event) ?? ""}`.trim();
|
|
9970
10028
|
default:
|
|
@@ -11174,17 +11232,44 @@ var DEFAULT_WARN_AFTER_MS = 1800;
|
|
|
11174
11232
|
var DEFAULT_FAIL_AFTER_MS = 3200;
|
|
11175
11233
|
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11176
11234
|
var firstNumber2 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
11235
|
+
var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
11236
|
+
var createTraceStageIndex = (events) => {
|
|
11237
|
+
const index = new Map;
|
|
11238
|
+
for (const event of events) {
|
|
11239
|
+
if (event.type !== "turn_latency.stage" || !event.turnId) {
|
|
11240
|
+
continue;
|
|
11241
|
+
}
|
|
11242
|
+
const stage = getString10(event.payload.stage);
|
|
11243
|
+
if (!stage) {
|
|
11244
|
+
continue;
|
|
11245
|
+
}
|
|
11246
|
+
const key = `${event.sessionId}:${event.turnId}`;
|
|
11247
|
+
const stages = index.get(key) ?? new Map;
|
|
11248
|
+
const previous = stages.get(stage);
|
|
11249
|
+
if (previous === undefined || event.at < previous) {
|
|
11250
|
+
stages.set(stage, event.at);
|
|
11251
|
+
}
|
|
11252
|
+
index.set(key, stages);
|
|
11253
|
+
}
|
|
11254
|
+
return index;
|
|
11255
|
+
};
|
|
11177
11256
|
var summarizeTurn = (sessionId, turn, options) => {
|
|
11178
|
-
const
|
|
11179
|
-
const
|
|
11180
|
-
const
|
|
11181
|
-
const
|
|
11182
|
-
const assistantTextStartedAt = turn.assistantText ?
|
|
11183
|
-
const
|
|
11257
|
+
const traceStages = options.stageIndex?.get(`${sessionId}:${turn.id}`);
|
|
11258
|
+
const firstTranscriptAt = traceStages?.get("speech_detected") ?? firstNumber2(turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
|
|
11259
|
+
const finalTranscriptAt = traceStages?.get("final_transcript") ?? firstNumber2(turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs));
|
|
11260
|
+
const committedAt = traceStages?.get("turn_committed") ?? turn.committedAt;
|
|
11261
|
+
const assistantTextStartedAt = traceStages?.get("assistant_text_started") ?? (turn.assistantText ? committedAt : undefined);
|
|
11262
|
+
const ttsSendStartedAt = traceStages?.get("tts_send_started");
|
|
11263
|
+
const ttsSendCompletedAt = traceStages?.get("tts_send_completed");
|
|
11264
|
+
const assistantAudioReceivedAt = traceStages?.get("assistant_audio_received");
|
|
11265
|
+
const commitAfterFirstMs = firstTranscriptAt === undefined ? undefined : Math.max(0, committedAt - firstTranscriptAt);
|
|
11266
|
+
const commitAfterFinalMs = finalTranscriptAt === undefined ? undefined : Math.max(0, committedAt - finalTranscriptAt);
|
|
11267
|
+
const totalEndAt = assistantAudioReceivedAt ?? assistantTextStartedAt ?? committedAt;
|
|
11268
|
+
const totalMs = firstTranscriptAt === undefined ? commitAfterFirstMs : Math.max(0, totalEndAt - firstTranscriptAt);
|
|
11184
11269
|
const status = totalMs === undefined ? "warn" : totalMs > options.failAfterMs ? "fail" : totalMs > options.warnAfterMs ? "warn" : "pass";
|
|
11185
11270
|
return {
|
|
11186
11271
|
assistantTextStartedAt,
|
|
11187
|
-
committedAt
|
|
11272
|
+
committedAt,
|
|
11188
11273
|
finalTranscriptAt,
|
|
11189
11274
|
firstTranscriptAt,
|
|
11190
11275
|
sessionId,
|
|
@@ -11193,7 +11278,19 @@ var summarizeTurn = (sessionId, turn, options) => {
|
|
|
11193
11278
|
{ label: "Final transcript to commit", valueMs: commitAfterFinalMs },
|
|
11194
11279
|
{
|
|
11195
11280
|
label: "Commit to assistant text",
|
|
11196
|
-
valueMs: assistantTextStartedAt === undefined ? undefined : 0
|
|
11281
|
+
valueMs: assistantTextStartedAt === undefined ? undefined : Math.max(0, assistantTextStartedAt - committedAt)
|
|
11282
|
+
},
|
|
11283
|
+
{
|
|
11284
|
+
label: "Assistant text to TTS send",
|
|
11285
|
+
valueMs: ttsSendStartedAt === undefined || assistantTextStartedAt === undefined ? undefined : Math.max(0, ttsSendStartedAt - assistantTextStartedAt)
|
|
11286
|
+
},
|
|
11287
|
+
{
|
|
11288
|
+
label: "TTS send duration",
|
|
11289
|
+
valueMs: ttsSendCompletedAt === undefined || ttsSendStartedAt === undefined ? undefined : Math.max(0, ttsSendCompletedAt - ttsSendStartedAt)
|
|
11290
|
+
},
|
|
11291
|
+
{
|
|
11292
|
+
label: "TTS to first audio",
|
|
11293
|
+
valueMs: assistantAudioReceivedAt === undefined || ttsSendCompletedAt === undefined ? undefined : Math.max(0, assistantAudioReceivedAt - ttsSendCompletedAt)
|
|
11197
11294
|
}
|
|
11198
11295
|
],
|
|
11199
11296
|
status,
|
|
@@ -11222,9 +11319,14 @@ var resolveSessions = async (options) => {
|
|
|
11222
11319
|
};
|
|
11223
11320
|
var summarizeVoiceTurnLatency = async (options) => {
|
|
11224
11321
|
const sessions = await resolveSessions(options);
|
|
11322
|
+
const traceEvents = options.traceStore ? await options.traceStore.list({
|
|
11323
|
+
limit: 1000,
|
|
11324
|
+
type: "turn_latency.stage"
|
|
11325
|
+
}) : [];
|
|
11326
|
+
const stageIndex = createTraceStageIndex(traceEvents);
|
|
11225
11327
|
const warnAfterMs = options.warnAfterMs ?? DEFAULT_WARN_AFTER_MS;
|
|
11226
11328
|
const failAfterMs = options.failAfterMs ?? DEFAULT_FAIL_AFTER_MS;
|
|
11227
|
-
const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn(session.id, turn, { failAfterMs, warnAfterMs }))).sort((left, right) => right.committedAt - left.committedAt);
|
|
11329
|
+
const turns = sessions.flatMap((session) => session.turns.map((turn) => summarizeTurn(session.id, turn, { failAfterMs, stageIndex, warnAfterMs }))).sort((left, right) => right.committedAt - left.committedAt);
|
|
11228
11330
|
const totals = turns.map((turn) => turn.totalMs).filter((value) => typeof value === "number");
|
|
11229
11331
|
const failed = turns.filter((turn) => turn.status === "fail").length;
|
|
11230
11332
|
const warnings = turns.filter((turn) => turn.status === "warn").length;
|
package/dist/testing/index.js
CHANGED
|
@@ -5216,7 +5216,7 @@ var createVoiceSession = (options) => {
|
|
|
5216
5216
|
} : undefined;
|
|
5217
5217
|
const appendTrace = async (input) => {
|
|
5218
5218
|
await options.trace?.append({
|
|
5219
|
-
at: Date.now(),
|
|
5219
|
+
at: input.at ?? Date.now(),
|
|
5220
5220
|
metadata: input.metadata,
|
|
5221
5221
|
payload: input.payload,
|
|
5222
5222
|
scenarioId: input.session?.scenarioId ?? options.scenarioId,
|
|
@@ -5225,6 +5225,13 @@ var createVoiceSession = (options) => {
|
|
|
5225
5225
|
type: input.type
|
|
5226
5226
|
});
|
|
5227
5227
|
};
|
|
5228
|
+
const appendTurnLatencyStage = async (input) => appendTrace({
|
|
5229
|
+
at: input.at,
|
|
5230
|
+
payload: { stage: input.stage },
|
|
5231
|
+
session: input.session,
|
|
5232
|
+
turnId: input.turnId,
|
|
5233
|
+
type: "turn_latency.stage"
|
|
5234
|
+
});
|
|
5228
5235
|
const phraseHints = options.phraseHints ?? [];
|
|
5229
5236
|
const lexicon = options.lexicon ?? [];
|
|
5230
5237
|
let socket = options.socket;
|
|
@@ -6175,6 +6182,13 @@ var createVoiceSession = (options) => {
|
|
|
6175
6182
|
turnId: activeTTSTurnId,
|
|
6176
6183
|
type: "audio"
|
|
6177
6184
|
});
|
|
6185
|
+
if (activeTTSTurnId) {
|
|
6186
|
+
await appendTurnLatencyStage({
|
|
6187
|
+
at: receivedAt,
|
|
6188
|
+
stage: "assistant_audio_received",
|
|
6189
|
+
turnId: activeTTSTurnId
|
|
6190
|
+
});
|
|
6191
|
+
}
|
|
6178
6192
|
});
|
|
6179
6193
|
});
|
|
6180
6194
|
openedSession.on("error", (event) => {
|
|
@@ -6233,6 +6247,7 @@ var createVoiceSession = (options) => {
|
|
|
6233
6247
|
voicemail: committedOutput?.voicemail
|
|
6234
6248
|
};
|
|
6235
6249
|
if (output?.assistantText) {
|
|
6250
|
+
const assistantTextStartedAt = Date.now();
|
|
6236
6251
|
await writeSession((currentSession) => {
|
|
6237
6252
|
setTurnResult(currentSession, turn.id, {
|
|
6238
6253
|
assistantText: output.assistantText
|
|
@@ -6243,6 +6258,12 @@ var createVoiceSession = (options) => {
|
|
|
6243
6258
|
turnId: turn.id,
|
|
6244
6259
|
type: "assistant"
|
|
6245
6260
|
});
|
|
6261
|
+
await appendTurnLatencyStage({
|
|
6262
|
+
at: assistantTextStartedAt,
|
|
6263
|
+
session,
|
|
6264
|
+
stage: "assistant_text_started",
|
|
6265
|
+
turnId: turn.id
|
|
6266
|
+
});
|
|
6246
6267
|
await appendTrace({
|
|
6247
6268
|
payload: {
|
|
6248
6269
|
text: output.assistantText,
|
|
@@ -6257,7 +6278,18 @@ var createVoiceSession = (options) => {
|
|
|
6257
6278
|
if (activeTTSSession) {
|
|
6258
6279
|
const ttsStartedAt = Date.now();
|
|
6259
6280
|
activeTTSTurnId = turn.id;
|
|
6281
|
+
await appendTurnLatencyStage({
|
|
6282
|
+
at: ttsStartedAt,
|
|
6283
|
+
session,
|
|
6284
|
+
stage: "tts_send_started",
|
|
6285
|
+
turnId: turn.id
|
|
6286
|
+
});
|
|
6260
6287
|
await activeTTSSession.send(output.assistantText);
|
|
6288
|
+
await appendTurnLatencyStage({
|
|
6289
|
+
session,
|
|
6290
|
+
stage: "tts_send_completed",
|
|
6291
|
+
turnId: turn.id
|
|
6292
|
+
});
|
|
6261
6293
|
await appendTrace({
|
|
6262
6294
|
payload: {
|
|
6263
6295
|
elapsedMs: Date.now() - ttsStartedAt,
|
|
@@ -6454,6 +6486,30 @@ var createVoiceSession = (options) => {
|
|
|
6454
6486
|
turnId: turn.id,
|
|
6455
6487
|
type: "turn.cost"
|
|
6456
6488
|
});
|
|
6489
|
+
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
6490
|
+
const finalTranscriptAt = turn.transcripts.filter((transcript) => transcript.isFinal).map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
6491
|
+
if (firstTranscriptAt !== undefined) {
|
|
6492
|
+
await appendTurnLatencyStage({
|
|
6493
|
+
at: firstTranscriptAt,
|
|
6494
|
+
session: updatedSession,
|
|
6495
|
+
stage: "speech_detected",
|
|
6496
|
+
turnId: turn.id
|
|
6497
|
+
});
|
|
6498
|
+
}
|
|
6499
|
+
if (finalTranscriptAt !== undefined) {
|
|
6500
|
+
await appendTurnLatencyStage({
|
|
6501
|
+
at: finalTranscriptAt,
|
|
6502
|
+
session: updatedSession,
|
|
6503
|
+
stage: "final_transcript",
|
|
6504
|
+
turnId: turn.id
|
|
6505
|
+
});
|
|
6506
|
+
}
|
|
6507
|
+
await appendTurnLatencyStage({
|
|
6508
|
+
at: turn.committedAt,
|
|
6509
|
+
session: updatedSession,
|
|
6510
|
+
stage: "turn_committed",
|
|
6511
|
+
turnId: turn.id
|
|
6512
|
+
});
|
|
6457
6513
|
await send({
|
|
6458
6514
|
turn,
|
|
6459
6515
|
type: "turn"
|
package/dist/trace.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.memory' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.handoff' | 'call.lifecycle' | 'client.barge_in' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn.transcript' | 'workflow.contract';
|
|
1
|
+
export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.memory' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.handoff' | 'call.lifecycle' | 'client.barge_in' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn_latency.stage' | 'turn.transcript' | 'workflow.contract';
|
|
2
2
|
export type VoiceTraceEvent<TPayload extends Record<string, unknown> = Record<string, unknown>> = {
|
|
3
3
|
at: number;
|
|
4
4
|
id?: string;
|
package/dist/turnLatency.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
|
+
import type { VoiceTraceEventStore } from './trace';
|
|
2
3
|
import type { VoiceSessionRecord, VoiceSessionStore } from './types';
|
|
3
4
|
export type VoiceTurnLatencyStatus = 'pass' | 'warn' | 'fail' | 'empty';
|
|
4
5
|
export type VoiceTurnLatencyStage = {
|
|
@@ -32,6 +33,7 @@ export type VoiceTurnLatencyOptions<TSession extends VoiceSessionRecord = VoiceS
|
|
|
32
33
|
sessionIds?: string[];
|
|
33
34
|
sessions?: TSession[];
|
|
34
35
|
store?: VoiceSessionStore<TSession>;
|
|
36
|
+
traceStore?: VoiceTraceEventStore;
|
|
35
37
|
warnAfterMs?: number;
|
|
36
38
|
failAfterMs?: number;
|
|
37
39
|
};
|