@absolutejs/voice 0.0.22-beta.71 → 0.0.22-beta.72

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
@@ -11,7 +11,7 @@ export { createVoiceToolIdempotencyKey, createVoiceToolRuntime } from './toolRun
11
11
  export { createVoiceToolContract, createVoiceToolContractHTMLHandler, createVoiceToolContractJSONHandler, createVoiceToolContractRoutes, createVoiceToolRuntimeContractDefaults, renderVoiceToolContractHTML, runVoiceToolContractSuite, runVoiceToolContract } from './toolContract';
12
12
  export { createVoiceTurnQualityHTMLHandler, createVoiceTurnQualityJSONHandler, createVoiceTurnQualityRoutes, renderVoiceTurnQualityHTML, summarizeVoiceTurnQuality } from './turnQuality';
13
13
  export { createVoiceOutcomeContractHTMLHandler, createVoiceOutcomeContractJSONHandler, createVoiceOutcomeContractRoutes, renderVoiceOutcomeContractHTML, runVoiceOutcomeContractSuite } from './outcomeContract';
14
- export { applyVoiceTelephonyOutcome, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
14
+ export { applyVoiceTelephonyOutcome, createMemoryVoiceTelephonyWebhookIdempotencyStore, createVoiceTelephonyOutcomePolicy, createVoiceTelephonyWebhookHandler, createVoiceTelephonyWebhookRoutes, parseVoiceTelephonyWebhookEvent, resolveVoiceTelephonyOutcome, signVoiceTwilioWebhook, verifyVoiceTwilioWebhookSignature, voiceTelephonyOutcomeToRouteResult } from './telephonyOutcome';
15
15
  export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileAssistantMemoryStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
16
16
  export { createVoiceAssistantMemoryHandle, createVoiceAssistantMemoryRecord, createVoiceMemoryAssistantMemoryStore, resolveVoiceAssistantMemoryNamespace } from './assistantMemory';
17
17
  export { createAnthropicVoiceAssistantModel, createGeminiVoiceAssistantModel, createJSONVoiceAssistantModel, createOpenAIVoiceAssistantModel, resolveVoiceProviderRoutingPolicyPreset, createVoiceProviderRouter } from './modelAdapters';
@@ -57,7 +57,7 @@ export type { VoiceProviderHealthStatus, VoiceProviderHealthSummary, VoiceProvid
57
57
  export type { VoiceProviderCapabilityDefinition, VoiceProviderCapabilityHandlerOptions, VoiceProviderCapabilityHTMLHandlerOptions, VoiceProviderCapabilityKind, VoiceProviderCapabilityOptions, VoiceProviderCapabilityReport, VoiceProviderCapabilityRoutesOptions, VoiceProviderCapabilitySummary } from './providerCapabilities';
58
58
  export type { VoiceTurnQualityHTMLHandlerOptions, VoiceTurnQualityItem, VoiceTurnQualityOptions, VoiceTurnQualityReport, VoiceTurnQualityRoutesOptions, VoiceTurnQualityStatus } from './turnQuality';
59
59
  export type { VoiceOutcomeContractDefinition, VoiceOutcomeContractHTMLHandlerOptions, VoiceOutcomeContractIssue, VoiceOutcomeContractOptions, VoiceOutcomeContractReport, VoiceOutcomeContractRoutesOptions, VoiceOutcomeContractStatus, VoiceOutcomeContractSuiteReport } from './outcomeContract';
60
- export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult } from './telephonyOutcome';
60
+ export type { VoiceTelephonyOutcomeAction, VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomePolicy, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyOutcomeRouteResult, VoiceTelephonyOutcomeStatusDecision, VoiceTelephonyWebhookDecision, VoiceTelephonyWebhookHandlerOptions, VoiceTelephonyWebhookIdempotencyStore, VoiceTelephonyWebhookParseInput, VoiceTelephonyWebhookProvider, VoiceTelephonyWebhookRoutesOptions, VoiceTelephonyWebhookVerificationResult, StoredVoiceTelephonyWebhookDecision } from './telephonyOutcome';
61
61
  export type { VoiceOpsConsoleLink, VoiceOpsConsoleReport, VoiceOpsConsoleRoutesOptions } from './opsConsoleRoutes';
62
62
  export type { VoiceQualityLink, VoiceQualityMetric, VoiceQualityReport, VoiceQualityRoutesOptions, VoiceQualityStatus, VoiceQualityThresholds } from './qualityRoutes';
63
63
  export type { VoiceResilienceIOSimulator, VoiceResilienceLink, VoiceResiliencePageData, VoiceResilienceRoutesOptions, VoiceResilienceSimulationProvider, VoiceRoutingDecisionSummary, VoiceRoutingDecisionSummaryOptions, VoiceRoutingEvent, VoiceRoutingEventKind } from './resilienceRoutes';
package/dist/index.js CHANGED
@@ -10610,6 +10610,15 @@ class VoiceTelephonyWebhookVerificationError extends Error {
10610
10610
  this.result = result;
10611
10611
  }
10612
10612
  }
10613
+ var createMemoryVoiceTelephonyWebhookIdempotencyStore = () => {
10614
+ const decisions = new Map;
10615
+ return {
10616
+ get: (key) => decisions.get(key),
10617
+ set: (key, decision) => {
10618
+ decisions.set(key, decision);
10619
+ }
10620
+ };
10621
+ };
10613
10622
  var normalizeToken = (value) => typeof value === "string" ? value.trim().toLowerCase().replace(/\s+/g, "-").replace(/_+/g, "-") : undefined;
10614
10623
  var firstString = (source, keys) => {
10615
10624
  for (const key of keys) {
@@ -11059,6 +11068,31 @@ var defaultSessionId = (input) => {
11059
11068
  "call_control_id"
11060
11069
  ]) ?? (typeof metadataSessionId === "string" ? metadataSessionId : undefined);
11061
11070
  };
11071
+ var defaultIdempotencyKey = (input) => {
11072
+ const payload = flattenPayload(input.body);
11073
+ const eventId = firstString(payload, [
11074
+ "id",
11075
+ "event_id",
11076
+ "eventId",
11077
+ "EventSid",
11078
+ "event_sid",
11079
+ "MessageSid",
11080
+ "message_sid",
11081
+ "CallSid",
11082
+ "call_sid",
11083
+ "CallUUID",
11084
+ "call_uuid",
11085
+ "callControlId",
11086
+ "call_control_id"
11087
+ ]);
11088
+ const status = normalizeToken(input.event.status) ?? "unknown";
11089
+ if (eventId) {
11090
+ return `${input.provider}:${eventId}:${status}`;
11091
+ }
11092
+ if (input.sessionId) {
11093
+ return `${input.provider}:${input.sessionId}:${status}`;
11094
+ }
11095
+ };
11062
11096
  var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
11063
11097
  const provider = options.provider ?? "generic";
11064
11098
  const query = input.query ?? {};
@@ -11093,6 +11127,31 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
11093
11127
  query,
11094
11128
  request: input.request
11095
11129
  }) ?? defaultSessionId({ body, event, query }));
11130
+ const idempotencyEnabled = options.idempotency?.enabled !== false;
11131
+ const idempotencyKey = idempotencyEnabled ? await (options.idempotency?.key?.({
11132
+ body,
11133
+ event,
11134
+ provider,
11135
+ query,
11136
+ request: input.request,
11137
+ sessionId
11138
+ }) ?? defaultIdempotencyKey({ body, event, provider, sessionId })) : undefined;
11139
+ const idempotencyStore = options.idempotency?.store;
11140
+ if (idempotencyKey && idempotencyStore) {
11141
+ const existing = await idempotencyStore.get(idempotencyKey);
11142
+ if (existing) {
11143
+ const duplicateDecision = {
11144
+ ...existing,
11145
+ duplicate: true
11146
+ };
11147
+ await options.onDecision?.({
11148
+ ...duplicateDecision,
11149
+ context: options.context,
11150
+ request: input.request
11151
+ });
11152
+ return duplicateDecision;
11153
+ }
11154
+ }
11096
11155
  const decision = resolveVoiceTelephonyOutcome(event, options.policy);
11097
11156
  const resultResolver = options.result;
11098
11157
  const result = typeof resultResolver === "function" ? await resultResolver({
@@ -11126,9 +11185,18 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
11126
11185
  applied,
11127
11186
  decision,
11128
11187
  event,
11188
+ idempotencyKey,
11129
11189
  routeResult,
11130
11190
  sessionId
11131
11191
  };
11192
+ if (idempotencyKey && idempotencyStore) {
11193
+ const now = Date.now();
11194
+ await idempotencyStore.set(idempotencyKey, {
11195
+ ...webhookDecision,
11196
+ createdAt: now,
11197
+ updatedAt: now
11198
+ });
11199
+ }
11132
11200
  await options.onDecision?.({
11133
11201
  ...webhookDecision,
11134
11202
  context: options.context,
@@ -15768,6 +15836,7 @@ export {
15768
15836
  createRiskyTurnCorrectionHandler,
15769
15837
  createPhraseHintCorrectionHandler,
15770
15838
  createOpenAIVoiceAssistantModel,
15839
+ createMemoryVoiceTelephonyWebhookIdempotencyStore,
15771
15840
  createJSONVoiceAssistantModel,
15772
15841
  createId,
15773
15842
  createGeminiVoiceAssistantModel,
@@ -55,10 +55,20 @@ export type VoiceTelephonyWebhookParseInput = {
55
55
  export type VoiceTelephonyWebhookDecision<TResult = unknown> = {
56
56
  applied: boolean;
57
57
  decision: VoiceTelephonyOutcomeDecision;
58
+ duplicate?: boolean;
58
59
  event: VoiceTelephonyOutcomeProviderEvent;
60
+ idempotencyKey?: string;
59
61
  routeResult: VoiceTelephonyOutcomeRouteResult<TResult>;
60
62
  sessionId?: string;
61
63
  };
64
+ export type StoredVoiceTelephonyWebhookDecision<TResult = unknown> = VoiceTelephonyWebhookDecision<TResult> & {
65
+ createdAt: number;
66
+ updatedAt: number;
67
+ };
68
+ export type VoiceTelephonyWebhookIdempotencyStore<TResult = unknown> = {
69
+ get: (key: string) => Promise<StoredVoiceTelephonyWebhookDecision<TResult> | undefined> | StoredVoiceTelephonyWebhookDecision<TResult> | undefined;
70
+ set: (key: string, decision: StoredVoiceTelephonyWebhookDecision<TResult>) => Promise<void> | void;
71
+ };
62
72
  export type VoiceTelephonyWebhookVerificationResult = {
63
73
  ok: true;
64
74
  } | {
@@ -75,6 +85,18 @@ export type VoiceTelephonyWebhookHandlerOptions<TContext = unknown, TSession ext
75
85
  request: Request;
76
86
  sessionId?: string;
77
87
  }) => Promise<VoiceSessionHandle<TContext, TSession, TResult> | undefined> | VoiceSessionHandle<TContext, TSession, TResult> | undefined;
88
+ idempotency?: {
89
+ enabled?: boolean;
90
+ key?: (input: {
91
+ body: unknown;
92
+ event: VoiceTelephonyOutcomeProviderEvent;
93
+ provider: VoiceTelephonyWebhookProvider;
94
+ query: Record<string, unknown>;
95
+ request: Request;
96
+ sessionId?: string;
97
+ }) => Promise<string | undefined> | string | undefined;
98
+ store?: VoiceTelephonyWebhookIdempotencyStore<TResult>;
99
+ };
78
100
  onDecision?: (input: VoiceTelephonyWebhookDecision<TResult> & {
79
101
  context: TContext;
80
102
  request: Request;
@@ -116,6 +138,7 @@ export declare class VoiceTelephonyWebhookVerificationError extends Error {
116
138
  result: VoiceTelephonyWebhookVerificationResult;
117
139
  constructor(result: VoiceTelephonyWebhookVerificationResult);
118
140
  }
141
+ export declare const createMemoryVoiceTelephonyWebhookIdempotencyStore: <TResult = unknown>() => VoiceTelephonyWebhookIdempotencyStore<TResult>;
119
142
  export declare const createVoiceTelephonyOutcomePolicy: (policy?: VoiceTelephonyOutcomePolicy) => Required<Pick<VoiceTelephonyOutcomePolicy, "completedStatuses" | "escalationStatuses" | "failedAsNoAnswer" | "failedStatuses" | "includeProviderPayload" | "machineDetectionVoicemailValues" | "noAnswerOnZeroDuration" | "noAnswerSipCodes" | "noAnswerStatuses" | "transferStatuses" | "voicemailStatuses">> & VoiceTelephonyOutcomePolicy;
120
143
  export declare const resolveVoiceTelephonyOutcome: (event: VoiceTelephonyOutcomeProviderEvent, policyInput?: VoiceTelephonyOutcomePolicy) => VoiceTelephonyOutcomeDecision;
121
144
  export declare const voiceTelephonyOutcomeToRouteResult: <TResult = unknown>(decision: VoiceTelephonyOutcomeDecision, result?: TResult) => VoiceTelephonyOutcomeRouteResult<TResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.71",
3
+ "version": "0.0.22-beta.72",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",