@absolutejs/voice 0.0.22-beta.487 → 0.0.22-beta.489

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.
@@ -0,0 +1,18 @@
1
+ export type VoiceBackchannelCue = {
2
+ audioUrl?: string;
3
+ metadata?: Record<string, unknown>;
4
+ text?: string;
5
+ };
6
+ export type VoiceBackchannelDriverOptions = {
7
+ cues?: ReadonlyArray<VoiceBackchannelCue>;
8
+ cueIntervalMs?: number;
9
+ cueIndex?: (index: number) => number;
10
+ minSpeechMs?: number;
11
+ onCue: (cue: VoiceBackchannelCue) => Promise<void> | void;
12
+ };
13
+ export type VoiceBackchannelDriver = {
14
+ noteSpeech: (timestampMs?: number) => void;
15
+ noteSilence: (timestampMs?: number) => void;
16
+ reset: () => void;
17
+ };
18
+ export declare const createVoiceBackchannelDriver: (options: VoiceBackchannelDriverOptions) => VoiceBackchannelDriver;
package/dist/index.d.ts CHANGED
@@ -81,6 +81,10 @@ export { describeVoiceAssistantMode, resolveVoiceAssistantMode, } from "./assist
81
81
  export type { VoiceAssistantMode, VoiceAssistantModality, VoiceAssistantModeDescriptor, VoiceSemanticVADConfig, } from "./assistantMode";
82
82
  export { createPunctuationSemanticTurnDetector, createRegexSemanticTurnDetector, } from "./semanticTurn";
83
83
  export { VOICE_WEBHOOK_SIGNATURE_HEADER, VOICE_WEBHOOK_TIMESTAMP_HEADER, extractVoiceWebhookSignatureFromHeaders, signVoiceWebhookBody, verifyVoiceWebhookSignature, } from "./webhookVerification";
84
+ export { createVoiceBackchannelDriver } from "./backchannel";
85
+ export type { VoiceBackchannelCue, VoiceBackchannelDriver, VoiceBackchannelDriverOptions, } from "./backchannel";
86
+ export { createVoiceIVRSession, describeVoiceIVRPlan, evaluateVoiceIVRPlan, } from "./ivrPlan";
87
+ export type { VoiceIVRBranch, VoiceIVRDecision, VoiceIVRInput, VoiceIVRMatch, VoiceIVRPlan, VoiceIVRSession, } from "./ivrPlan";
84
88
  export { VOICE_CALLER_MEMORY_KEY, buildVoiceCallerMemoryNamespace, createVoiceCallerMemoryNamespace, summarizeVoiceCallerTranscript, } from "./callerMemory";
85
89
  export type { CreateVoiceCallerMemoryNamespaceOptions, SummarizeVoiceCallerTranscriptOptions, VoiceCallerIdentity, VoiceCallerMemoryCompletion, VoiceCallerMemorySnapshot, VoiceCallerMemorySummarizerInput, } from "./callerMemory";
86
90
  export { aggregateVoiceTurnLatencySpans, buildOTELSpanId, buildOTELTraceId, buildVoiceOTELPayload, createVoiceOTELHTTPExporter, } from "./otelExporter";
package/dist/index.js CHANGED
@@ -35475,6 +35475,154 @@ var extractVoiceWebhookSignatureFromHeaders = (headers) => {
35475
35475
  timestamp: get(VOICE_WEBHOOK_TIMESTAMP_HEADER)
35476
35476
  };
35477
35477
  };
35478
+ // src/backchannel.ts
35479
+ var DEFAULT_CUES = [
35480
+ { text: "mm-hmm" },
35481
+ { text: "I see" },
35482
+ { text: "right" },
35483
+ { text: "go on" }
35484
+ ];
35485
+ var createVoiceBackchannelDriver = (options) => {
35486
+ const cues = options.cues ?? DEFAULT_CUES;
35487
+ const minSpeechMs = options.minSpeechMs ?? 2500;
35488
+ const cueIntervalMs = options.cueIntervalMs ?? 2500;
35489
+ const cueIndexFn = options.cueIndex ?? ((index) => index % Math.max(cues.length, 1));
35490
+ let speechStartedAt;
35491
+ let lastCueAt;
35492
+ let cueCount = 0;
35493
+ let firing = false;
35494
+ const tryFire = async (now) => {
35495
+ if (firing || cues.length === 0) {
35496
+ return;
35497
+ }
35498
+ if (speechStartedAt === undefined) {
35499
+ return;
35500
+ }
35501
+ const elapsed = now - speechStartedAt;
35502
+ if (elapsed < minSpeechMs) {
35503
+ return;
35504
+ }
35505
+ if (lastCueAt !== undefined && now - lastCueAt < cueIntervalMs) {
35506
+ return;
35507
+ }
35508
+ const cue = cues[cueIndexFn(cueCount)];
35509
+ if (!cue) {
35510
+ return;
35511
+ }
35512
+ firing = true;
35513
+ try {
35514
+ await options.onCue(cue);
35515
+ } finally {
35516
+ firing = false;
35517
+ lastCueAt = now;
35518
+ cueCount += 1;
35519
+ }
35520
+ };
35521
+ return {
35522
+ noteSpeech: (timestampMs) => {
35523
+ const now = timestampMs ?? Date.now();
35524
+ if (speechStartedAt === undefined) {
35525
+ speechStartedAt = now;
35526
+ }
35527
+ tryFire(now);
35528
+ },
35529
+ noteSilence: (timestampMs) => {
35530
+ const now = timestampMs ?? Date.now();
35531
+ if (lastCueAt !== undefined && now - lastCueAt > cueIntervalMs * 2) {
35532
+ speechStartedAt = undefined;
35533
+ }
35534
+ },
35535
+ reset: () => {
35536
+ speechStartedAt = undefined;
35537
+ lastCueAt = undefined;
35538
+ cueCount = 0;
35539
+ }
35540
+ };
35541
+ };
35542
+ // src/ivrPlan.ts
35543
+ var speechMatchesPattern = (pattern, speech) => {
35544
+ const normalized = speech.toLowerCase().trim();
35545
+ if (pattern instanceof RegExp) {
35546
+ return pattern.test(normalized);
35547
+ }
35548
+ const target = pattern.toLowerCase().trim();
35549
+ if (!target) {
35550
+ return false;
35551
+ }
35552
+ return normalized.includes(target);
35553
+ };
35554
+ var digitsMatchPattern = (pattern, input) => {
35555
+ return input === pattern;
35556
+ };
35557
+ var branchMatches = (branch, input) => {
35558
+ const matcher = branch.match;
35559
+ if (matcher.digit && input.digits) {
35560
+ if (input.digits === matcher.digit) {
35561
+ return true;
35562
+ }
35563
+ }
35564
+ if (matcher.digits && input.digits) {
35565
+ if (digitsMatchPattern(matcher.digits, input.digits)) {
35566
+ return true;
35567
+ }
35568
+ }
35569
+ if (matcher.speech && input.speech) {
35570
+ if (speechMatchesPattern(matcher.speech, input.speech)) {
35571
+ return true;
35572
+ }
35573
+ }
35574
+ return false;
35575
+ };
35576
+ var evaluateVoiceIVRPlan = (plan, input) => {
35577
+ if (!input.digits && !input.speech) {
35578
+ return { reason: "timeout" };
35579
+ }
35580
+ for (const branch of plan.branches) {
35581
+ if (branchMatches(branch, input)) {
35582
+ return { branch, reason: "matched" };
35583
+ }
35584
+ }
35585
+ if (plan.fallbackBranchId) {
35586
+ const fallback = plan.branches.find((branch) => branch.id === plan.fallbackBranchId);
35587
+ if (fallback) {
35588
+ return { branch: fallback, reason: "fallback" };
35589
+ }
35590
+ }
35591
+ return { reason: "no-match" };
35592
+ };
35593
+ var createVoiceIVRSession = (plan) => {
35594
+ const maxAttempts = plan.maxAttempts ?? 3;
35595
+ let attempts = 0;
35596
+ return {
35597
+ attempt: () => attempts,
35598
+ decide: (input) => {
35599
+ attempts += 1;
35600
+ return evaluateVoiceIVRPlan(plan, input);
35601
+ },
35602
+ exhausted: () => attempts >= maxAttempts,
35603
+ reset: () => {
35604
+ attempts = 0;
35605
+ }
35606
+ };
35607
+ };
35608
+ var describeVoiceIVRPlan = (plan) => {
35609
+ const lines = [plan.greeting];
35610
+ for (const branch of plan.branches) {
35611
+ const triggers = [];
35612
+ if (branch.match.digit) {
35613
+ triggers.push(`press ${branch.match.digit}`);
35614
+ }
35615
+ if (branch.match.digits) {
35616
+ triggers.push(`press ${branch.match.digits}`);
35617
+ }
35618
+ if (branch.match.speech) {
35619
+ triggers.push(`say "${branch.match.speech instanceof RegExp ? branch.match.speech.source : branch.match.speech}"`);
35620
+ }
35621
+ lines.push(`- ${branch.label}: ${triggers.join(" or ")}`);
35622
+ }
35623
+ return lines.join(`
35624
+ `);
35625
+ };
35478
35626
  // src/callerMemory.ts
35479
35627
  var VOICE_CALLER_MEMORY_KEY = "caller-memory-snapshot";
35480
35628
  var normalizeIdentifier = (value) => value.trim().replace(/[^a-zA-Z0-9+@._-]/g, "-").toLowerCase();
@@ -46553,6 +46701,7 @@ export {
46553
46701
  evaluateVoiceMediaPipelineEvidence,
46554
46702
  evaluateVoiceLiveOpsEvidence,
46555
46703
  evaluateVoiceLiveOpsControlEvidence,
46704
+ evaluateVoiceIVRPlan,
46556
46705
  evaluateVoiceGuardrailPolicy,
46557
46706
  evaluateVoiceDataControlEvidence,
46558
46707
  evaluateVoiceCompetitiveCoverage,
@@ -46562,6 +46711,7 @@ export {
46562
46711
  evaluateVoiceAgentSquadContractEvidence,
46563
46712
  encodeTwilioMulawBase64,
46564
46713
  encodePcmAsWav,
46714
+ describeVoiceIVRPlan,
46565
46715
  describeVoiceAssistantMode,
46566
46716
  deliverVoiceTraceEventsToSinks,
46567
46717
  deliverVoiceObservabilityExport,
@@ -46807,6 +46957,7 @@ export {
46807
46957
  createVoiceIncidentBundleRoutes,
46808
46958
  createVoiceInMemoryRealCallProfileRecoveryJobStore,
46809
46959
  createVoiceInMemoryMonitorRegistry,
46960
+ createVoiceIVRSession,
46810
46961
  createVoiceHubSpotTaskUpdateSink,
46811
46962
  createVoiceHubSpotTaskSyncSinks,
46812
46963
  createVoiceHubSpotTaskSink,
@@ -46872,6 +47023,7 @@ export {
46872
47023
  createVoiceBrowserMediaRoutes,
46873
47024
  createVoiceBrowserCallProfileRoutes,
46874
47025
  createVoiceBargeInRoutes,
47026
+ createVoiceBackchannelDriver,
46875
47027
  createVoiceAuditTrailRoutes,
46876
47028
  createVoiceAuditSinkStore,
46877
47029
  createVoiceAuditSinkDeliveryWorkerLoop,
@@ -0,0 +1,40 @@
1
+ export type VoiceIVRMatch = {
2
+ digit?: string;
3
+ digits?: string;
4
+ speech?: string | RegExp;
5
+ };
6
+ export type VoiceIVRBranch = {
7
+ assistantId?: string;
8
+ description?: string;
9
+ id: string;
10
+ label: string;
11
+ match: VoiceIVRMatch;
12
+ metadata?: Record<string, unknown>;
13
+ target?: string;
14
+ };
15
+ export type VoiceIVRPlan = {
16
+ branches: readonly VoiceIVRBranch[];
17
+ fallbackBranchId?: string;
18
+ greeting: string;
19
+ maxAttempts?: number;
20
+ noMatchPrompt?: string;
21
+ timeoutMs?: number;
22
+ timeoutPrompt?: string;
23
+ };
24
+ export type VoiceIVRInput = {
25
+ digits?: string;
26
+ speech?: string;
27
+ };
28
+ export type VoiceIVRDecision = {
29
+ branch?: VoiceIVRBranch;
30
+ reason: "matched" | "no-match" | "fallback" | "timeout";
31
+ };
32
+ export declare const evaluateVoiceIVRPlan: (plan: VoiceIVRPlan, input: VoiceIVRInput) => VoiceIVRDecision;
33
+ export type VoiceIVRSession = {
34
+ attempt: () => number;
35
+ decide: (input: VoiceIVRInput) => VoiceIVRDecision;
36
+ exhausted: () => boolean;
37
+ reset: () => void;
38
+ };
39
+ export declare const createVoiceIVRSession: (plan: VoiceIVRPlan) => VoiceIVRSession;
40
+ export declare const describeVoiceIVRPlan: (plan: VoiceIVRPlan) => string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.487",
3
+ "version": "0.0.22-beta.489",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",