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

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
@@ -71,6 +71,10 @@ export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, crea
71
71
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
72
72
  export { createAIVoiceModel } from "./aiVoiceModel";
73
73
  export type { CreateAIVoiceModelOptions } from "./aiVoiceModel";
74
+ export { createVoiceAIJudgeCompletion, createVoiceLLMJudge, } from "./llmJudge";
75
+ export type { CreateVoiceAIJudgeCompletionOptions, CreateVoiceLLMJudgeOptions, VoiceLLMJudge, VoiceLLMJudgeCompletion, VoiceLLMJudgeCriterionVerdict, VoiceLLMJudgeInput, VoiceLLMJudgeRubric, VoiceLLMJudgeRubricCriterion, VoiceLLMJudgeVerdict, } from "./llmJudge";
76
+ export { DEFAULT_VOICE_REDACTION_PATTERNS, createVoiceTranscriptRedactor, redactVoiceTranscript, } from "./redaction";
77
+ export type { CreateVoiceTranscriptRedactorOptions, VoiceRedactionPattern, VoiceTranscriptRedactor, } from "./redaction";
74
78
  export { DEFAULT_VOICE_PRICE_BOOK, createVoiceCostAccountant, } from "./costAccounting";
75
79
  export type { CreateVoiceCostAccountantOptions, VoiceCostAccountant, VoiceCostBreakdown, VoiceCostLLMRecord, VoiceCostSTTRecord, VoiceCostTTSRecord, VoiceCostTelephonyRecord, VoicePriceBook, VoiceProviderRates, } from "./costAccounting";
76
80
  export { createMonologueAMDDetector } from "./amdDetector";
package/dist/index.js CHANGED
@@ -4505,11 +4505,13 @@ var createVoiceSession = (options) => {
4505
4505
  fallbackSession.on("final", ({ transcript }) => {
4506
4506
  fallbackFinalReceived = true;
4507
4507
  lastFallbackTranscriptAt = Date.now();
4508
- fallbackTranscripts.push(cloneTranscript(transcript));
4508
+ const next = options.redact ? options.redact(transcript) : transcript;
4509
+ fallbackTranscripts.push(cloneTranscript(next));
4509
4510
  }),
4510
4511
  fallbackSession.on("partial", ({ transcript }) => {
4511
4512
  lastFallbackTranscriptAt = Date.now();
4512
- fallbackTranscripts.push(cloneTranscript(transcript));
4513
+ const next = options.redact ? options.redact(transcript) : transcript;
4514
+ fallbackTranscripts.push(cloneTranscript(next));
4513
4515
  }),
4514
4516
  fallbackSession.on("endOfTurn", () => {
4515
4517
  fallbackEndOfTurnReceived = true;
@@ -4827,10 +4829,12 @@ var createVoiceSession = (options) => {
4827
4829
  });
4828
4830
  };
4829
4831
  openedSession.on("partial", ({ transcript }) => {
4830
- runAdapterEvent("adapter.partial", () => handlePartial(transcript));
4832
+ const next = options.redact ? options.redact(transcript) : transcript;
4833
+ runAdapterEvent("adapter.partial", () => handlePartial(next));
4831
4834
  });
4832
4835
  openedSession.on("final", ({ transcript }) => {
4833
- runAdapterEvent("adapter.final", () => handleFinal(transcript));
4836
+ const next = options.redact ? options.redact(transcript) : transcript;
4837
+ runAdapterEvent("adapter.final", () => handleFinal(next));
4834
4838
  });
4835
4839
  openedSession.on("endOfTurn", ({ reason }) => {
4836
4840
  runAdapterEvent("adapter.endOfTurn", async () => {
@@ -34977,6 +34981,174 @@ var createAIVoiceModel = (options) => ({
34977
34981
  return output;
34978
34982
  }
34979
34983
  });
34984
+ // src/llmJudge.ts
34985
+ var DEFAULT_SYSTEM_PROMPT = "You are an impartial evaluator scoring a voice-agent transcript against a rubric. " + "For each criterion, decide pass/fail and give a one-sentence rationale grounded in the transcript. " + 'Respond with strict JSON: {"criteria":[{"criterionId":"\u2026","passed":true,"rationale":"\u2026"}],"summary":"\u2026"}.';
34986
+ var buildPrompt = (rubric, input) => {
34987
+ const criteriaBlock = rubric.criteria.map((criterion) => `- ${criterion.id}${criterion.required ? " (required)" : ""}: ${criterion.description}`).join(`
34988
+ `);
34989
+ const metadataBlock = input.metadata ? `
34990
+ Metadata:
34991
+ ${JSON.stringify(input.metadata, null, 2)}
34992
+ ` : "";
34993
+ return `Rubric criteria:
34994
+ ${criteriaBlock}
34995
+ ${metadataBlock}
34996
+ Transcript:
34997
+ ${input.transcript}
34998
+
34999
+ Return JSON only.`;
35000
+ };
35001
+ var extractJson = (raw) => {
35002
+ const trimmed = raw.trim();
35003
+ if (!trimmed) {
35004
+ throw new Error("LLM judge returned an empty response");
35005
+ }
35006
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(trimmed);
35007
+ const candidate = fenced ? fenced[1].trim() : trimmed;
35008
+ try {
35009
+ return JSON.parse(candidate);
35010
+ } catch {
35011
+ const start = candidate.indexOf("{");
35012
+ const end = candidate.lastIndexOf("}");
35013
+ if (start >= 0 && end > start) {
35014
+ return JSON.parse(candidate.slice(start, end + 1));
35015
+ }
35016
+ throw new Error(`LLM judge response was not valid JSON: ${raw.slice(0, 200)}`);
35017
+ }
35018
+ };
35019
+ var parseCriteria = (payload, rubric) => {
35020
+ if (!payload || typeof payload !== "object") {
35021
+ throw new Error("LLM judge response is not a JSON object");
35022
+ }
35023
+ const root = payload;
35024
+ const criteriaRaw = root.criteria;
35025
+ if (!Array.isArray(criteriaRaw)) {
35026
+ throw new Error("LLM judge response is missing the 'criteria' array");
35027
+ }
35028
+ const verdictById = new Map;
35029
+ for (const entry of criteriaRaw) {
35030
+ if (!entry || typeof entry !== "object") {
35031
+ continue;
35032
+ }
35033
+ const record = entry;
35034
+ const criterionId = typeof record.criterionId === "string" ? record.criterionId : typeof record.id === "string" ? record.id : undefined;
35035
+ if (!criterionId) {
35036
+ continue;
35037
+ }
35038
+ verdictById.set(criterionId, {
35039
+ criterionId,
35040
+ passed: record.passed === true,
35041
+ rationale: typeof record.rationale === "string" ? record.rationale : ""
35042
+ });
35043
+ }
35044
+ const criteria = rubric.criteria.map((criterion) => verdictById.get(criterion.id) ?? {
35045
+ criterionId: criterion.id,
35046
+ passed: false,
35047
+ rationale: "Judge did not return a verdict for this criterion."
35048
+ });
35049
+ return {
35050
+ criteria,
35051
+ summary: typeof root.summary === "string" ? root.summary : undefined
35052
+ };
35053
+ };
35054
+ var scoreVerdict = (rubric, criteria) => {
35055
+ const totalWeight = rubric.criteria.reduce((sum, criterion) => sum + (criterion.weight ?? 1), 0);
35056
+ if (totalWeight === 0) {
35057
+ return { passed: false, score: 0 };
35058
+ }
35059
+ const weightById = new Map(rubric.criteria.map((criterion) => [criterion.id, criterion.weight ?? 1]));
35060
+ const requiredIds = new Set(rubric.criteria.filter((criterion) => criterion.required).map((criterion) => criterion.id));
35061
+ let earned = 0;
35062
+ let allRequiredPassed = true;
35063
+ for (const verdict of criteria) {
35064
+ if (verdict.passed) {
35065
+ earned += weightById.get(verdict.criterionId) ?? 1;
35066
+ } else if (requiredIds.has(verdict.criterionId)) {
35067
+ allRequiredPassed = false;
35068
+ }
35069
+ }
35070
+ const score = earned / totalWeight;
35071
+ const minPassScore = rubric.minPassScore ?? 1;
35072
+ return {
35073
+ passed: allRequiredPassed && score >= minPassScore,
35074
+ score
35075
+ };
35076
+ };
35077
+ var createVoiceLLMJudge = (options) => ({
35078
+ evaluate: async (input) => {
35079
+ const prompt = buildPrompt(options.rubric, input);
35080
+ const raw = await options.completion({
35081
+ prompt,
35082
+ systemPrompt: options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
35083
+ });
35084
+ const parsed = parseCriteria(extractJson(raw), options.rubric);
35085
+ const { passed, score } = scoreVerdict(options.rubric, parsed.criteria);
35086
+ return {
35087
+ criteria: parsed.criteria,
35088
+ passed,
35089
+ score,
35090
+ summary: parsed.summary
35091
+ };
35092
+ },
35093
+ rubric: options.rubric
35094
+ });
35095
+ var createVoiceAIJudgeCompletion = (options) => async ({ prompt, systemPrompt }) => {
35096
+ const messages = [
35097
+ { content: prompt, role: "user" }
35098
+ ];
35099
+ const stream = options.provider.stream({
35100
+ messages,
35101
+ model: options.model,
35102
+ systemPrompt
35103
+ });
35104
+ let buffered = "";
35105
+ for await (const chunk of stream) {
35106
+ if (chunk.type === "text") {
35107
+ buffered += chunk.content;
35108
+ }
35109
+ }
35110
+ return buffered;
35111
+ };
35112
+ // src/redaction.ts
35113
+ var DEFAULT_VOICE_REDACTION_PATTERNS = [
35114
+ {
35115
+ label: "credit-card",
35116
+ regex: /\b(?:\d[ -]?){13,19}\b/g,
35117
+ replacement: "[REDACTED:CC]"
35118
+ },
35119
+ {
35120
+ label: "ssn",
35121
+ regex: /\b\d{3}-\d{2}-\d{4}\b/g,
35122
+ replacement: "[REDACTED:SSN]"
35123
+ },
35124
+ {
35125
+ label: "email",
35126
+ regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
35127
+ replacement: "[REDACTED:EMAIL]"
35128
+ },
35129
+ {
35130
+ label: "phone",
35131
+ regex: /\b(?:\+?1[ .-]?)?\(?\d{3}\)?[ .-]?\d{3}[ .-]?\d{4}\b/g,
35132
+ replacement: "[REDACTED:PHONE]"
35133
+ }
35134
+ ];
35135
+ var createVoiceTranscriptRedactor = (options = {}) => {
35136
+ const patterns = options.patterns ?? DEFAULT_VOICE_REDACTION_PATTERNS;
35137
+ return (transcript) => {
35138
+ if (!transcript.text) {
35139
+ return transcript;
35140
+ }
35141
+ let redacted = transcript.text;
35142
+ for (const pattern of patterns) {
35143
+ redacted = redacted.replace(pattern.regex, pattern.replacement ?? `[REDACTED:${pattern.label.toUpperCase()}]`);
35144
+ }
35145
+ if (redacted === transcript.text) {
35146
+ return transcript;
35147
+ }
35148
+ return { ...transcript, text: redacted };
35149
+ };
35150
+ };
35151
+ var redactVoiceTranscript = (transcript, patterns = DEFAULT_VOICE_REDACTION_PATTERNS) => createVoiceTranscriptRedactor({ patterns })(transcript);
34980
35152
  // src/costAccounting.ts
34981
35153
  var DEFAULT_VOICE_PRICE_BOOK = {
34982
35154
  "anthropic:claude-opus-4-5": {
@@ -45884,6 +46056,7 @@ export {
45884
46056
  renderVoiceAuditHTML,
45885
46057
  renderVoiceAuditDeliveryHTML,
45886
46058
  renderVoiceAssistantHealthHTML,
46059
+ redactVoiceTranscript,
45887
46060
  redactVoiceTraceText,
45888
46061
  redactVoiceTraceEvents,
45889
46062
  redactVoiceTraceEvent,
@@ -46005,6 +46178,7 @@ export {
46005
46178
  createVoiceTurnLatencyJSONHandler,
46006
46179
  createVoiceTurnLatencyHTMLHandler,
46007
46180
  createVoiceTransferCallTool,
46181
+ createVoiceTranscriptRedactor,
46008
46182
  createVoiceTraceTimelineRoutes,
46009
46183
  createVoiceTraceSinkStore,
46010
46184
  createVoiceTraceSinkDeliveryWorkerLoop,
@@ -46206,6 +46380,7 @@ export {
46206
46380
  createVoiceLinearIssueUpdateSink,
46207
46381
  createVoiceLinearIssueSyncSinks,
46208
46382
  createVoiceLinearIssueSink,
46383
+ createVoiceLLMJudge,
46209
46384
  createVoiceIntegrationSinkWorkerLoop,
46210
46385
  createVoiceIntegrationSinkWorker,
46211
46386
  createVoiceIntegrationHTTPSink,
@@ -46301,6 +46476,7 @@ export {
46301
46476
  createVoiceAgentTool,
46302
46477
  createVoiceAgentSquad,
46303
46478
  createVoiceAgent,
46479
+ createVoiceAIJudgeCompletion,
46304
46480
  createTwilioVoiceRoutes,
46305
46481
  createTwilioVoiceResponse,
46306
46482
  createTwilioMediaStreamBridge,
@@ -46475,6 +46651,7 @@ export {
46475
46651
  acknowledgeVoiceMonitorIssue,
46476
46652
  VOICE_LIVE_OPS_ACTIONS,
46477
46653
  TURN_PROFILE_DEFAULTS,
46654
+ DEFAULT_VOICE_REDACTION_PATTERNS,
46478
46655
  DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
46479
46656
  DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS,
46480
46657
  DEFAULT_VOICE_PRICE_BOOK
@@ -0,0 +1,45 @@
1
+ import type { AIProviderConfig } from "@absolutejs/ai";
2
+ export type VoiceLLMJudgeRubricCriterion = {
3
+ description: string;
4
+ id: string;
5
+ required?: boolean;
6
+ weight?: number;
7
+ };
8
+ export type VoiceLLMJudgeRubric = {
9
+ criteria: VoiceLLMJudgeRubricCriterion[];
10
+ minPassScore?: number;
11
+ };
12
+ export type VoiceLLMJudgeCriterionVerdict = {
13
+ criterionId: string;
14
+ passed: boolean;
15
+ rationale: string;
16
+ };
17
+ export type VoiceLLMJudgeVerdict = {
18
+ criteria: VoiceLLMJudgeCriterionVerdict[];
19
+ passed: boolean;
20
+ score: number;
21
+ summary?: string;
22
+ };
23
+ export type VoiceLLMJudgeInput = {
24
+ metadata?: Record<string, unknown>;
25
+ transcript: string;
26
+ };
27
+ export type VoiceLLMJudgeCompletion = (input: {
28
+ prompt: string;
29
+ systemPrompt?: string;
30
+ }) => Promise<string>;
31
+ export type CreateVoiceLLMJudgeOptions = {
32
+ completion: VoiceLLMJudgeCompletion;
33
+ rubric: VoiceLLMJudgeRubric;
34
+ systemPrompt?: string;
35
+ };
36
+ export type VoiceLLMJudge = {
37
+ evaluate: (input: VoiceLLMJudgeInput) => Promise<VoiceLLMJudgeVerdict>;
38
+ rubric: VoiceLLMJudgeRubric;
39
+ };
40
+ export declare const createVoiceLLMJudge: (options: CreateVoiceLLMJudgeOptions) => VoiceLLMJudge;
41
+ export type CreateVoiceAIJudgeCompletionOptions = {
42
+ model: string;
43
+ provider: AIProviderConfig;
44
+ };
45
+ export declare const createVoiceAIJudgeCompletion: (options: CreateVoiceAIJudgeCompletionOptions) => VoiceLLMJudgeCompletion;
@@ -0,0 +1,13 @@
1
+ import type { Transcript } from "./types";
2
+ export type VoiceRedactionPattern = {
3
+ label: string;
4
+ replacement?: string;
5
+ regex: RegExp;
6
+ };
7
+ export declare const DEFAULT_VOICE_REDACTION_PATTERNS: VoiceRedactionPattern[];
8
+ export type VoiceTranscriptRedactor = (transcript: Transcript) => Transcript;
9
+ export type CreateVoiceTranscriptRedactorOptions = {
10
+ patterns?: VoiceRedactionPattern[];
11
+ };
12
+ export declare const createVoiceTranscriptRedactor: (options?: CreateVoiceTranscriptRedactorOptions) => VoiceTranscriptRedactor;
13
+ export declare const redactVoiceTranscript: (transcript: Transcript, patterns?: VoiceRedactionPattern[]) => Transcript;
@@ -6473,11 +6473,13 @@ var createVoiceSession = (options) => {
6473
6473
  fallbackSession.on("final", ({ transcript }) => {
6474
6474
  fallbackFinalReceived = true;
6475
6475
  lastFallbackTranscriptAt = Date.now();
6476
- fallbackTranscripts.push(cloneTranscript(transcript));
6476
+ const next = options.redact ? options.redact(transcript) : transcript;
6477
+ fallbackTranscripts.push(cloneTranscript(next));
6477
6478
  }),
6478
6479
  fallbackSession.on("partial", ({ transcript }) => {
6479
6480
  lastFallbackTranscriptAt = Date.now();
6480
- fallbackTranscripts.push(cloneTranscript(transcript));
6481
+ const next = options.redact ? options.redact(transcript) : transcript;
6482
+ fallbackTranscripts.push(cloneTranscript(next));
6481
6483
  }),
6482
6484
  fallbackSession.on("endOfTurn", () => {
6483
6485
  fallbackEndOfTurnReceived = true;
@@ -6795,10 +6797,12 @@ var createVoiceSession = (options) => {
6795
6797
  });
6796
6798
  };
6797
6799
  openedSession.on("partial", ({ transcript }) => {
6798
- runAdapterEvent("adapter.partial", () => handlePartial(transcript));
6800
+ const next = options.redact ? options.redact(transcript) : transcript;
6801
+ runAdapterEvent("adapter.partial", () => handlePartial(next));
6799
6802
  });
6800
6803
  openedSession.on("final", ({ transcript }) => {
6801
- runAdapterEvent("adapter.final", () => handleFinal(transcript));
6804
+ const next = options.redact ? options.redact(transcript) : transcript;
6805
+ runAdapterEvent("adapter.final", () => handleFinal(next));
6802
6806
  });
6803
6807
  openedSession.on("endOfTurn", ({ reason }) => {
6804
6808
  runAdapterEvent("adapter.endOfTurn", async () => {
package/dist/types.d.ts CHANGED
@@ -730,6 +730,7 @@ export type CreateVoiceSessionOptions<TContext = unknown, TSession extends Voice
730
730
  costTelephony?: {
731
731
  provider?: string;
732
732
  };
733
+ redact?: import("./redaction").VoiceTranscriptRedactor;
733
734
  reconnect: Required<VoiceReconnectConfig>;
734
735
  phraseHints?: VoicePhraseHint[];
735
736
  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.480",
3
+ "version": "0.0.22-beta.482",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",