@absolutejs/voice 0.0.22-beta.482 → 0.0.22-beta.483

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.d.ts CHANGED
@@ -77,6 +77,8 @@ export { DEFAULT_VOICE_REDACTION_PATTERNS, createVoiceTranscriptRedactor, redact
77
77
  export type { CreateVoiceTranscriptRedactorOptions, VoiceRedactionPattern, VoiceTranscriptRedactor, } from "./redaction";
78
78
  export { DEFAULT_VOICE_PRICE_BOOK, createVoiceCostAccountant, } from "./costAccounting";
79
79
  export type { CreateVoiceCostAccountantOptions, VoiceCostAccountant, VoiceCostBreakdown, VoiceCostLLMRecord, VoiceCostSTTRecord, VoiceCostTTSRecord, VoiceCostTelephonyRecord, VoicePriceBook, VoiceProviderRates, } from "./costAccounting";
80
+ export { createPunctuationSemanticTurnDetector, createRegexSemanticTurnDetector, } from "./semanticTurn";
81
+ export type { CreatePunctuationSemanticTurnDetectorOptions, CreateRegexSemanticTurnDetectorOptions, VoiceSemanticTurnDetector, VoiceSemanticTurnInput, VoiceSemanticTurnVerdict, } from "./semanticTurn";
80
82
  export { createMonologueAMDDetector } from "./amdDetector";
81
83
  export type { MonologueAMDDetectorOptions, VoiceAMDDetector, VoiceAMDDetectorInput, VoiceAMDVerdict, } from "./amdDetector";
82
84
  export { createVoiceRAGTool } from "./ragTool";
package/dist/index.js CHANGED
@@ -4786,6 +4786,18 @@ var createVoiceSession = (options) => {
4786
4786
  session,
4787
4787
  type: "turn.transcript"
4788
4788
  });
4789
+ if (options.semanticTurnDetector) {
4790
+ const verdict = await Promise.resolve(options.semanticTurnDetector.evaluate({
4791
+ lastFinalTranscript: transcript,
4792
+ partialText: session.currentTurn.partialText,
4793
+ silenceMs: session.currentTurn.silenceStartedAt !== undefined ? Date.now() - session.currentTurn.silenceStartedAt : 0,
4794
+ transcripts: session.currentTurn.transcripts
4795
+ }));
4796
+ if (verdict.endOfTurn) {
4797
+ clearSilenceTimer();
4798
+ await requestTurnCommit("vendor");
4799
+ }
4800
+ }
4789
4801
  };
4790
4802
  const resumePendingTurnCommit = (session) => {
4791
4803
  const pendingText = buildTurnText(session.currentTurn.transcripts, session.currentTurn.partialText, {
@@ -35292,6 +35304,76 @@ var createVoiceCostAccountant = (options = {}) => {
35292
35304
  })
35293
35305
  };
35294
35306
  };
35307
+ // src/semanticTurn.ts
35308
+ var DEFAULT_END_PUNCTUATION = [".", "?", "!"];
35309
+ var DEFAULT_FILLER_WORDS = [
35310
+ "uh",
35311
+ "um",
35312
+ "er",
35313
+ "ah",
35314
+ "like",
35315
+ "you know",
35316
+ "i mean",
35317
+ "well",
35318
+ "so"
35319
+ ];
35320
+ var stripTerminalPunctuation = (text) => text.replace(/[\s.?!]+$/u, "").trim();
35321
+ var createPunctuationSemanticTurnDetector = (options = {}) => {
35322
+ const endPunctuation = options.endPunctuation ?? DEFAULT_END_PUNCTUATION;
35323
+ const fillerWords = (options.fillerWords ?? DEFAULT_FILLER_WORDS).map((word) => word.toLowerCase());
35324
+ const minPartialWords = options.minPartialWords ?? 2;
35325
+ return {
35326
+ evaluate: ({ lastFinalTranscript, partialText }) => {
35327
+ const candidate = partialText.trim().length > 0 ? partialText : lastFinalTranscript?.text ?? "";
35328
+ const trimmed = candidate.trim();
35329
+ if (!trimmed) {
35330
+ return { endOfTurn: false, reason: "empty" };
35331
+ }
35332
+ const wordCount = trimmed.split(/\s+/u).filter(Boolean).length;
35333
+ if (wordCount < minPartialWords) {
35334
+ return { endOfTurn: false, reason: "below-min-words" };
35335
+ }
35336
+ const lastChar = trimmed.at(-1);
35337
+ const endsWithTerminal = typeof lastChar === "string" && endPunctuation.includes(lastChar);
35338
+ if (!endsWithTerminal) {
35339
+ return { endOfTurn: false, reason: "no-terminal-punctuation" };
35340
+ }
35341
+ const lastWord = stripTerminalPunctuation(trimmed).split(/\s+/u).at(-1)?.toLowerCase();
35342
+ if (lastWord && fillerWords.includes(lastWord)) {
35343
+ return { endOfTurn: false, reason: "trailing-filler" };
35344
+ }
35345
+ return {
35346
+ confidence: 0.9,
35347
+ endOfTurn: true,
35348
+ reason: "terminal-punctuation"
35349
+ };
35350
+ }
35351
+ };
35352
+ };
35353
+ var createRegexSemanticTurnDetector = (options) => {
35354
+ const minPartialWords = options.minPartialWords ?? 2;
35355
+ return {
35356
+ evaluate: ({ lastFinalTranscript, partialText }) => {
35357
+ const candidate = partialText.trim().length > 0 ? partialText : lastFinalTranscript?.text ?? "";
35358
+ const trimmed = candidate.trim();
35359
+ if (!trimmed) {
35360
+ return { endOfTurn: false, reason: "empty" };
35361
+ }
35362
+ const wordCount = trimmed.split(/\s+/u).filter(Boolean).length;
35363
+ if (wordCount < minPartialWords) {
35364
+ return { endOfTurn: false, reason: "below-min-words" };
35365
+ }
35366
+ const match = options.endPattern.exec(trimmed);
35367
+ if (!match) {
35368
+ return { endOfTurn: false, reason: "pattern-miss" };
35369
+ }
35370
+ return {
35371
+ endOfTurn: true,
35372
+ reason: "pattern-match"
35373
+ };
35374
+ }
35375
+ };
35376
+ };
35295
35377
  // src/amdDetector.ts
35296
35378
  var createMonologueAMDDetector = (options = {}) => {
35297
35379
  const minMonologueMs = options.minMonologueMs ?? 8000;
@@ -46489,6 +46571,8 @@ export {
46489
46571
  createStoredVoiceExternalObjectMap,
46490
46572
  createStoredVoiceCallReviewArtifact,
46491
46573
  createRiskyTurnCorrectionHandler,
46574
+ createRegexSemanticTurnDetector,
46575
+ createPunctuationSemanticTurnDetector,
46492
46576
  createPlivoVoiceRoutes,
46493
46577
  createPlivoVoiceResponse,
46494
46578
  createPlivoMediaStreamBridge,
@@ -0,0 +1,27 @@
1
+ import type { Transcript } from "./types";
2
+ export type VoiceSemanticTurnInput = {
3
+ audioLevel?: number;
4
+ lastFinalTranscript?: Transcript;
5
+ partialText: string;
6
+ silenceMs: number;
7
+ transcripts: Transcript[];
8
+ };
9
+ export type VoiceSemanticTurnVerdict = {
10
+ confidence?: number;
11
+ endOfTurn: boolean;
12
+ reason?: string;
13
+ };
14
+ export type VoiceSemanticTurnDetector = {
15
+ evaluate: (input: VoiceSemanticTurnInput) => Promise<VoiceSemanticTurnVerdict> | VoiceSemanticTurnVerdict;
16
+ };
17
+ export type CreatePunctuationSemanticTurnDetectorOptions = {
18
+ endPunctuation?: ReadonlyArray<string>;
19
+ fillerWords?: ReadonlyArray<string>;
20
+ minPartialWords?: number;
21
+ };
22
+ export declare const createPunctuationSemanticTurnDetector: (options?: CreatePunctuationSemanticTurnDetectorOptions) => VoiceSemanticTurnDetector;
23
+ export type CreateRegexSemanticTurnDetectorOptions = {
24
+ endPattern: RegExp;
25
+ minPartialWords?: number;
26
+ };
27
+ export declare const createRegexSemanticTurnDetector: (options: CreateRegexSemanticTurnDetectorOptions) => VoiceSemanticTurnDetector;
@@ -6754,6 +6754,18 @@ var createVoiceSession = (options) => {
6754
6754
  session,
6755
6755
  type: "turn.transcript"
6756
6756
  });
6757
+ if (options.semanticTurnDetector) {
6758
+ const verdict = await Promise.resolve(options.semanticTurnDetector.evaluate({
6759
+ lastFinalTranscript: transcript,
6760
+ partialText: session.currentTurn.partialText,
6761
+ silenceMs: session.currentTurn.silenceStartedAt !== undefined ? Date.now() - session.currentTurn.silenceStartedAt : 0,
6762
+ transcripts: session.currentTurn.transcripts
6763
+ }));
6764
+ if (verdict.endOfTurn) {
6765
+ clearSilenceTimer();
6766
+ await requestTurnCommit("vendor");
6767
+ }
6768
+ }
6757
6769
  };
6758
6770
  const resumePendingTurnCommit = (session) => {
6759
6771
  const pendingText = buildTurnText(session.currentTurn.transcripts, session.currentTurn.partialText, {
package/dist/types.d.ts CHANGED
@@ -731,6 +731,7 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
731
731
  provider?: string;
732
732
  };
733
733
  redact?: import("./redaction").VoiceTranscriptRedactor;
734
+ semanticTurnDetector?: import("./semanticTurn").VoiceSemanticTurnDetector;
734
735
  reconnect: Required<VoiceReconnectConfig>;
735
736
  phraseHints?: VoicePhraseHint[];
736
737
  sessionMetadata?: Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.482",
3
+ "version": "0.0.22-beta.483",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",