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

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';
@@ -22,15 +22,15 @@ export { createVoiceQualityRoutes, evaluateVoiceQuality, renderVoiceQualityHTML
22
22
  export { createVoiceResilienceRoutes, createVoiceRoutingDecisionSummary, listVoiceRoutingEvents, renderVoiceResilienceHTML, summarizeVoiceRoutingDecision } from './resilienceRoutes';
23
23
  export { createVoiceSTTProviderRouter, createVoiceTTSProviderRouter } from './providerAdapters';
24
24
  export { buildVoiceTraceReplay, createVoiceMemoryTraceSinkDeliveryStore, createVoiceTraceHTTPSink, createVoiceMemoryTraceEventStore, createVoiceTraceSinkDeliveryId, createVoiceTraceSinkDeliveryRecord, createVoiceTraceSinkStore, createVoiceTraceEvent, createVoiceTraceEventId, deliverVoiceTraceEventsToSinks, evaluateVoiceTrace, exportVoiceTrace, filterVoiceTraceEvents, pruneVoiceTraceEvents, redactVoiceTraceEvent, redactVoiceTraceEvents, redactVoiceTraceText, renderVoiceTraceHTML, renderVoiceTraceMarkdown, resolveVoiceTraceRedactionOptions, selectVoiceTraceEventsForPrune, summarizeVoiceTrace } from './trace';
25
- export { createVoiceSQLiteExternalObjectMapStore, createVoiceSQLiteIntegrationEventStore, createVoiceSQLiteReviewStore, createVoiceSQLiteRuntimeStorage, createVoiceSQLiteSessionStore, createVoiceSQLiteTaskStore, createVoiceSQLiteTraceSinkDeliveryStore, createVoiceSQLiteTraceEventStore } from './sqliteStore';
26
- export { createVoicePostgresExternalObjectMapStore, createVoicePostgresIntegrationEventStore, createVoicePostgresReviewStore, createVoicePostgresRuntimeStorage, createVoicePostgresSessionStore, createVoicePostgresTaskStore, createVoicePostgresTraceSinkDeliveryStore, createVoicePostgresTraceEventStore } from './postgresStore';
25
+ export { createVoiceSQLiteExternalObjectMapStore, createVoiceSQLiteIntegrationEventStore, createVoiceSQLiteReviewStore, createVoiceSQLiteRuntimeStorage, createVoiceSQLiteSessionStore, createVoiceSQLiteTaskStore, createVoiceSQLiteTelephonyWebhookIdempotencyStore, createVoiceSQLiteTraceSinkDeliveryStore, createVoiceSQLiteTraceEventStore } from './sqliteStore';
26
+ export { createVoicePostgresExternalObjectMapStore, createVoicePostgresIntegrationEventStore, createVoicePostgresReviewStore, createVoicePostgresRuntimeStorage, createVoicePostgresSessionStore, createVoicePostgresTaskStore, createVoicePostgresTelephonyWebhookIdempotencyStore, createVoicePostgresTraceSinkDeliveryStore, createVoicePostgresTraceEventStore } from './postgresStore';
27
27
  export { createVoiceS3ReviewStore } from './s3Store';
28
28
  export { createVoiceMemoryStore } from './memoryStore';
29
29
  export { createVoiceCRMActivitySink, createVoiceHelpdeskTicketSink, createVoiceIntegrationHTTPSink, createVoiceHubSpotTaskSink, createVoiceHubSpotTaskSyncSinks, createVoiceHubSpotTaskUpdateSink, createVoiceLinearIssueSink, createVoiceLinearIssueSyncSinks, createVoiceLinearIssueUpdateSink, createVoiceZendeskTicketSink, createVoiceZendeskTicketSyncSinks, createVoiceZendeskTicketUpdateSink, deliverVoiceIntegrationEventToSinks } from './opsSinks';
30
30
  export { createVoiceOpsWebhookEnvelope, createVoiceOpsWebhookReceiverRoutes, createVoiceOpsWebhookSink, verifyVoiceOpsWebhookSignature } from './opsWebhook';
31
31
  export { applyVoiceHandoffDeliveryResult, createVoiceHandoffDeliveryRecord, createVoiceMemoryHandoffDeliveryStore, createVoiceTwilioRedirectHandoffAdapter, createVoiceWebhookHandoffAdapter, deliverVoiceHandoff, deliverVoiceHandoffDelivery } from './handoff';
32
32
  export { createVoiceHandoffHealthHTMLHandler, createVoiceHandoffHealthJSONHandler, createVoiceHandoffHealthRoutes, renderVoiceHandoffHealthHTML, summarizeVoiceHandoffHealth } from './handoffHealth';
33
- export { createVoiceHandoffDeliveryWorker, createVoiceHandoffDeliveryWorkerLoop, createVoiceIntegrationSinkWorker, createVoiceIntegrationSinkWorkerLoop, createVoiceOpsTaskWorker, createVoiceOpsTaskProcessorWorker, createVoiceOpsTaskProcessorWorkerLoop, createVoiceRedisIdempotencyStore, createVoiceRedisTaskLeaseCoordinator, createVoiceTraceSinkDeliveryWorker, createVoiceTraceSinkDeliveryWorkerLoop, createVoiceWebhookDeliveryWorker, createVoiceWebhookDeliveryWorkerLoop, summarizeVoiceHandoffDeliveries, summarizeVoiceTraceSinkDeliveries, summarizeVoiceOpsTaskQueue, summarizeVoiceIntegrationEvents } from './queue';
33
+ export { createVoiceHandoffDeliveryWorker, createVoiceHandoffDeliveryWorkerLoop, createVoiceIntegrationSinkWorker, createVoiceIntegrationSinkWorkerLoop, createVoiceOpsTaskWorker, createVoiceOpsTaskProcessorWorker, createVoiceOpsTaskProcessorWorkerLoop, createVoiceRedisIdempotencyStore, createVoiceRedisTelephonyWebhookIdempotencyStore, createVoiceRedisTaskLeaseCoordinator, createVoiceTraceSinkDeliveryWorker, createVoiceTraceSinkDeliveryWorkerLoop, createVoiceWebhookDeliveryWorker, createVoiceWebhookDeliveryWorkerLoop, summarizeVoiceHandoffDeliveries, summarizeVoiceTraceSinkDeliveries, summarizeVoiceOpsTaskQueue, summarizeVoiceIntegrationEvents } from './queue';
34
34
  export { assignVoiceOpsTask, applyVoiceOpsTaskAssignmentRule, applyVoiceOpsTaskPolicy, buildVoiceOpsTaskFromReview, buildVoiceOpsTaskFromSLABreach, claimVoiceOpsTask, completeVoiceOpsTask, createVoiceExternalObjectMap, createVoiceExternalObjectMapId, createVoiceCallCompletedEvent, createVoiceTaskSLABreachedEvent, deadLetterVoiceOpsTask, deliverVoiceIntegrationEvent, failVoiceOpsTask, hasVoiceOpsTaskSLABreach, heartbeatVoiceOpsTask, isVoiceOpsTaskOverdue, markVoiceOpsTaskSLABreached, matchesVoiceOpsTaskAssignmentRule, resolveVoiceOpsTaskAgeBucket, createVoiceIntegrationEvent, createVoiceReviewSavedEvent, resolveVoiceOpsTaskAssignment, resolveVoiceOpsTaskPolicy, requeueVoiceOpsTask, createVoiceTaskCreatedEvent, createVoiceTaskUpdatedEvent, listVoiceOpsTasks, reopenVoiceOpsTask, startVoiceOpsTask, summarizeVoiceOpsTaskAnalytics, summarizeVoiceOpsTasks, withVoiceIntegrationEventId, withVoiceOpsTaskId } from './ops';
35
35
  export { createVoiceSession } from './session';
36
36
  export { createVoiceCallReviewFromSession, recordVoiceRuntimeOps } from './runtimeOps';
@@ -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';
@@ -76,7 +76,7 @@ export type { StoredVoiceCallReviewArtifact, VoiceCallReviewArtifact, VoiceCallR
76
76
  export type { VoiceFileRuntimeStorage, VoiceFileStoreOptions } from './fileStore';
77
77
  export type { StoredVoiceTraceEvent, VoiceTraceEvaluation, VoiceTraceEvaluationOptions, VoiceTraceEvent, VoiceTraceEventFilter, VoiceTraceEventStore, VoiceTraceEventType, VoiceTraceIssue, VoiceTraceIssueSeverity, VoiceTraceHTTPSinkOptions, VoiceTracePruneFilter, VoiceTracePruneOptions, VoiceTracePruneResult, VoiceTraceRedactionConfig, VoiceTraceRedactionOptions, VoiceTraceRedactionReplacement, VoiceResolvedTraceRedactionOptions, VoiceTraceSink, VoiceTraceSinkDeliveryQueueStatus, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryResult, VoiceTraceSinkDeliveryStatus, VoiceTraceSinkDeliveryStore, VoiceTraceSinkFanoutResult, VoiceTraceSinkStoreOptions, VoiceTraceSummary } from './trace';
78
78
  export type { VoicePostgresClient, VoicePostgresRuntimeStorage, VoicePostgresStoreOptions } from './postgresStore';
79
- export type { VoiceOpsTaskLease, VoiceOpsTaskWorker, VoiceOpsTaskWorkerOptions, VoiceHandoffDeliveryQueueSummary, VoiceHandoffDeliveryWorkerLoop, VoiceHandoffDeliveryWorkerLoopOptions, VoiceHandoffDeliveryWorkerOptions, VoiceHandoffDeliveryWorkerResult, VoiceIdempotencyStore, VoiceIntegrationEventQueueSummary, VoiceIntegrationSinkWorkerLoop, VoiceIntegrationSinkWorkerLoopOptions, VoiceIntegrationSinkWorkerOptions, VoiceIntegrationSinkWorkerResult, VoiceRedisIdempotencyClient, VoiceRedisIdempotencyStoreOptions, VoiceRedisTaskLeaseClient, VoiceRedisTaskLeaseCoordinator, VoiceRedisTaskLeaseCoordinatorOptions, VoiceTraceSinkDeliveryQueueSummary, VoiceTraceSinkDeliveryWorkerLoop, VoiceTraceSinkDeliveryWorkerLoopOptions, VoiceTraceSinkDeliveryWorkerOptions, VoiceTraceSinkDeliveryWorkerResult, VoiceOpsTaskClaimFilters, VoiceWebhookDeliveryWorkerLoop, VoiceWebhookDeliveryWorkerLoopOptions, VoiceWebhookDeliveryWorkerOptions, VoiceWebhookDeliveryWorkerResult, VoiceOpsTaskProcessorWorkerLoop, VoiceOpsTaskProcessorWorkerLoopOptions, VoiceOpsTaskProcessorWorkerOptions, VoiceOpsTaskProcessorWorkerResult, VoiceOpsTaskQueueSummary } from './queue';
79
+ export type { VoiceOpsTaskLease, VoiceOpsTaskWorker, VoiceOpsTaskWorkerOptions, VoiceHandoffDeliveryQueueSummary, VoiceHandoffDeliveryWorkerLoop, VoiceHandoffDeliveryWorkerLoopOptions, VoiceHandoffDeliveryWorkerOptions, VoiceHandoffDeliveryWorkerResult, VoiceIdempotencyStore, VoiceIntegrationEventQueueSummary, VoiceIntegrationSinkWorkerLoop, VoiceIntegrationSinkWorkerLoopOptions, VoiceIntegrationSinkWorkerOptions, VoiceIntegrationSinkWorkerResult, VoiceRedisIdempotencyClient, VoiceRedisIdempotencyStoreOptions, VoiceRedisTelephonyWebhookIdempotencyClient, VoiceRedisTelephonyWebhookIdempotencyStoreOptions, VoiceRedisTaskLeaseClient, VoiceRedisTaskLeaseCoordinator, VoiceRedisTaskLeaseCoordinatorOptions, VoiceTraceSinkDeliveryQueueSummary, VoiceTraceSinkDeliveryWorkerLoop, VoiceTraceSinkDeliveryWorkerLoopOptions, VoiceTraceSinkDeliveryWorkerOptions, VoiceTraceSinkDeliveryWorkerResult, VoiceOpsTaskClaimFilters, VoiceWebhookDeliveryWorkerLoop, VoiceWebhookDeliveryWorkerLoopOptions, VoiceWebhookDeliveryWorkerOptions, VoiceWebhookDeliveryWorkerResult, VoiceOpsTaskProcessorWorkerLoop, VoiceOpsTaskProcessorWorkerLoopOptions, VoiceOpsTaskProcessorWorkerOptions, VoiceOpsTaskProcessorWorkerResult, VoiceOpsTaskQueueSummary } from './queue';
80
80
  export type { VoiceS3ReviewStoreClient, VoiceS3ReviewStoreFile, VoiceS3ReviewStoreOptions } from './s3Store';
81
81
  export type { VoiceSQLiteRuntimeStorage, VoiceSQLiteStoreOptions } from './sqliteStore';
82
82
  export type { StoredVoiceIntegrationEvent, StoredVoiceExternalObjectMap, StoredVoiceOpsTask, VoiceExternalObjectMap, VoiceExternalObjectMapStore, VoiceOpsTaskAgeBucket, VoiceOpsTaskAnalyticsOptions, VoiceOpsTaskAnalyticsSummary, VoiceOpsTaskAssignmentRule, VoiceOpsTaskAssignmentRuleCondition, VoiceOpsTaskAssignmentRules, VoiceOpsTaskAssigneeAnalytics, VoiceOpsDispositionTaskPolicies, VoiceOpsSLABreachPolicy, VoiceIntegrationDeliveryStatus, VoiceIntegrationEvent, VoiceIntegrationEventStore, VoiceIntegrationSinkDelivery, VoiceIntegrationEventType, VoiceIntegrationWebhookConfig, VoiceOpsTask, VoiceOpsTaskHistoryEntry, VoiceOpsTaskKind, VoiceOpsTaskPolicy, VoiceOpsTaskPriority, VoiceOpsTaskStatus, VoiceOpsTaskStore, VoiceOpsTaskSummary, VoiceOpsTaskWorkerAnalytics } from './ops';
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,
@@ -12908,6 +12976,12 @@ var createSQLiteTraceSinkDeliveryStoreWithDatabase = (database, tableName) => cr
12908
12976
  getSortAt: (value) => value.createdAt,
12909
12977
  tableName
12910
12978
  });
12979
+ var createSQLiteTelephonyWebhookIdempotencyStoreWithDatabase = (database, tableName) => createSQLiteRecordStore({
12980
+ database,
12981
+ decorate: (_id, value) => value,
12982
+ getSortAt: (value) => value.updatedAt,
12983
+ tableName
12984
+ });
12911
12985
  var createVoiceSQLiteSessionStore = (options) => createSQLiteSessionStoreWithDatabase(openVoiceSQLiteDatabase(options.path), resolveTableName({
12912
12986
  fallback: "sessions",
12913
12987
  options
@@ -12936,6 +13010,10 @@ var createVoiceSQLiteTraceSinkDeliveryStore = (options) => createSQLiteTraceSink
12936
13010
  fallback: "trace_deliveries",
12937
13011
  options
12938
13012
  }));
13013
+ var createVoiceSQLiteTelephonyWebhookIdempotencyStore = (options) => createSQLiteTelephonyWebhookIdempotencyStoreWithDatabase(openVoiceSQLiteDatabase(options.path), resolveTableName({
13014
+ fallback: "telephony_webhook_idempotency",
13015
+ options
13016
+ }));
12939
13017
  var createVoiceSQLiteRuntimeStorage = (options) => {
12940
13018
  const database = openVoiceSQLiteDatabase(options.path);
12941
13019
  return {
@@ -13151,6 +13229,15 @@ var createPostgresTraceSinkDeliveryStoreWithClient = (client, options) => create
13151
13229
  }),
13152
13230
  sql: client
13153
13231
  });
13232
+ var createPostgresTelephonyWebhookIdempotencyStoreWithClient = (client, options) => createPostgresRecordStore({
13233
+ decorate: (_id, value) => value,
13234
+ getSortAt: (value) => value.updatedAt,
13235
+ qualifiedTableName: resolveQualifiedTableName({
13236
+ fallback: "telephony_webhook_idempotency",
13237
+ options
13238
+ }),
13239
+ sql: client
13240
+ });
13154
13241
  var createVoicePostgresSessionStore = (options) => createPostgresSessionStoreWithClient(createVoicePostgresClient(options), options);
13155
13242
  var createVoicePostgresReviewStore = (options) => createPostgresReviewStoreWithClient(createVoicePostgresClient(options), options);
13156
13243
  var createVoicePostgresTaskStore = (options) => createPostgresTaskStoreWithClient(createVoicePostgresClient(options), options);
@@ -13158,6 +13245,7 @@ var createVoicePostgresIntegrationEventStore = (options) => createPostgresEventS
13158
13245
  var createVoicePostgresExternalObjectMapStore = (options) => createPostgresExternalObjectMapStoreWithClient(createVoicePostgresClient(options), options);
13159
13246
  var createVoicePostgresTraceEventStore = (options) => createPostgresTraceEventStoreWithClient(createVoicePostgresClient(options), options);
13160
13247
  var createVoicePostgresTraceSinkDeliveryStore = (options) => createPostgresTraceSinkDeliveryStoreWithClient(createVoicePostgresClient(options), options);
13248
+ var createVoicePostgresTelephonyWebhookIdempotencyStore = (options) => createPostgresTelephonyWebhookIdempotencyStoreWithClient(createVoicePostgresClient(options), options);
13161
13249
  var createVoicePostgresRuntimeStorage = (options) => {
13162
13250
  const client = createVoicePostgresClient(options);
13163
13251
  return {
@@ -13427,6 +13515,7 @@ return 0
13427
13515
  `;
13428
13516
  var getLeaseKey = (prefix, taskId) => `${prefix}:${taskId}`;
13429
13517
  var getIdempotencyKey = (prefix, key) => `${prefix}:${key}`;
13518
+ var getTelephonyWebhookIdempotencyKey = (prefix, key) => `${prefix}:${key}`;
13430
13519
  var parseLeaseValue = (taskId, value, ttlMs) => {
13431
13520
  if (!value || ttlMs <= 0) {
13432
13521
  return null;
@@ -13734,6 +13823,26 @@ var createVoiceRedisIdempotencyStore = (options = {}) => {
13734
13823
  }
13735
13824
  };
13736
13825
  };
13826
+ var createVoiceRedisTelephonyWebhookIdempotencyStore = (options = {}) => {
13827
+ const client = options.client ?? new Bun.RedisClient(options.url);
13828
+ const keyPrefix = options.keyPrefix?.trim() || "voice:telephony-webhook";
13829
+ const defaultTtlSeconds = options.ttlSeconds;
13830
+ return {
13831
+ get: async (key) => {
13832
+ const value = await client.get(getTelephonyWebhookIdempotencyKey(keyPrefix, key));
13833
+ return value ? JSON.parse(value) : undefined;
13834
+ },
13835
+ set: async (key, decision) => {
13836
+ const redisKey = getTelephonyWebhookIdempotencyKey(keyPrefix, key);
13837
+ const value = JSON.stringify(decision);
13838
+ if (typeof defaultTtlSeconds === "number" && defaultTtlSeconds > 0) {
13839
+ await client.set(redisKey, value, "EX", String(Math.ceil(defaultTtlSeconds)));
13840
+ return;
13841
+ }
13842
+ await client.set(redisKey, value);
13843
+ }
13844
+ };
13845
+ };
13737
13846
  var createVoiceWebhookDeliveryWorker = (options) => {
13738
13847
  const allowedStatuses = options.statuses ?? ["pending", "failed"];
13739
13848
  const leaseMs = Math.max(1, options.leaseMs ?? 30000);
@@ -15666,6 +15775,7 @@ export {
15666
15775
  createVoiceSTTProviderRouter,
15667
15776
  createVoiceSQLiteTraceSinkDeliveryStore,
15668
15777
  createVoiceSQLiteTraceEventStore,
15778
+ createVoiceSQLiteTelephonyWebhookIdempotencyStore,
15669
15779
  createVoiceSQLiteTaskStore,
15670
15780
  createVoiceSQLiteSessionStore,
15671
15781
  createVoiceSQLiteRuntimeStorage,
@@ -15676,6 +15786,7 @@ export {
15676
15786
  createVoiceRoutingDecisionSummary,
15677
15787
  createVoiceReviewSavedEvent,
15678
15788
  createVoiceResilienceRoutes,
15789
+ createVoiceRedisTelephonyWebhookIdempotencyStore,
15679
15790
  createVoiceRedisTaskLeaseCoordinator,
15680
15791
  createVoiceRedisIdempotencyStore,
15681
15792
  createVoiceQualityRoutes,
@@ -15688,6 +15799,7 @@ export {
15688
15799
  createVoiceProviderCapabilityHTMLHandler,
15689
15800
  createVoicePostgresTraceSinkDeliveryStore,
15690
15801
  createVoicePostgresTraceEventStore,
15802
+ createVoicePostgresTelephonyWebhookIdempotencyStore,
15691
15803
  createVoicePostgresTaskStore,
15692
15804
  createVoicePostgresSessionStore,
15693
15805
  createVoicePostgresRuntimeStorage,
@@ -15768,6 +15880,7 @@ export {
15768
15880
  createRiskyTurnCorrectionHandler,
15769
15881
  createPhraseHintCorrectionHandler,
15770
15882
  createOpenAIVoiceAssistantModel,
15883
+ createMemoryVoiceTelephonyWebhookIdempotencyStore,
15771
15884
  createJSONVoiceAssistantModel,
15772
15885
  createId,
15773
15886
  createGeminiVoiceAssistantModel,
@@ -1,6 +1,7 @@
1
1
  import { type StoredVoiceTraceEvent, type VoiceTraceSinkDeliveryRecord, type VoiceTraceSinkDeliveryStore, type VoiceTraceEventStore } from './trace';
2
2
  import type { StoredVoiceIntegrationEvent, StoredVoiceExternalObjectMap, StoredVoiceOpsTask, VoiceExternalObjectMapStore, VoiceIntegrationEventStore, VoiceOpsTaskStore } from './ops';
3
3
  import type { StoredVoiceCallReviewArtifact, VoiceCallReviewStore } from './testing/review';
4
+ import type { VoiceTelephonyWebhookIdempotencyStore } from './telephonyOutcome';
4
5
  import type { VoiceSessionRecord, VoiceSessionStore } from './types';
5
6
  export type VoicePostgresClient = {
6
7
  unsafe: <TRow extends Record<string, unknown> = Record<string, unknown>>(query: string, parameters?: unknown[]) => Promise<TRow[]>;
@@ -28,4 +29,5 @@ export declare const createVoicePostgresIntegrationEventStore: <TEvent extends S
28
29
  export declare const createVoicePostgresExternalObjectMapStore: <TMapping extends StoredVoiceExternalObjectMap = StoredVoiceExternalObjectMap>(options: VoicePostgresStoreOptions) => VoiceExternalObjectMapStore<TMapping>;
29
30
  export declare const createVoicePostgresTraceEventStore: <TEvent extends StoredVoiceTraceEvent = StoredVoiceTraceEvent>(options: VoicePostgresStoreOptions) => VoiceTraceEventStore<TEvent>;
30
31
  export declare const createVoicePostgresTraceSinkDeliveryStore: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(options: VoicePostgresStoreOptions) => VoiceTraceSinkDeliveryStore<TDelivery>;
32
+ export declare const createVoicePostgresTelephonyWebhookIdempotencyStore: <TResult = unknown>(options: VoicePostgresStoreOptions) => VoiceTelephonyWebhookIdempotencyStore<TResult>;
31
33
  export declare const createVoicePostgresRuntimeStorage: <TSession extends VoiceSessionRecord = VoiceSessionRecord, TReview extends StoredVoiceCallReviewArtifact = StoredVoiceCallReviewArtifact, TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask, TEvent extends StoredVoiceIntegrationEvent = StoredVoiceIntegrationEvent, TMapping extends StoredVoiceExternalObjectMap = StoredVoiceExternalObjectMap, TTrace extends StoredVoiceTraceEvent = StoredVoiceTraceEvent, TTraceDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(options: VoicePostgresStoreOptions) => VoicePostgresRuntimeStorage<TSession, TReview, TTask, TEvent, TMapping, TTrace, TTraceDelivery>;
package/dist/queue.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { RedisClient } from 'bun';
2
2
  import type { VoiceIntegrationSink } from './opsSinks';
3
3
  import type { VoiceTraceRedactionConfig, VoiceTraceSink, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryStore, VoiceTraceSinkDeliveryQueueStatus } from './trace';
4
+ import type { VoiceTelephonyWebhookIdempotencyStore } from './telephonyOutcome';
4
5
  import type { StoredVoiceHandoffDelivery, VoiceHandoffAdapter, VoiceHandoffDeliveryQueueStatus, VoiceHandoffDeliveryStore, VoiceSessionHandle, VoiceSessionRecord } from './types';
5
6
  import type { VoiceOpsTaskPriority, StoredVoiceOpsTask, StoredVoiceIntegrationEvent, VoiceIntegrationDeliveryStatus, VoiceIntegrationEventStore, VoiceIntegrationWebhookConfig, VoiceOpsTaskKind, VoiceOpsTaskStatus, VoiceOpsTaskStore } from './ops';
6
7
  export type VoiceOpsTaskLease = {
@@ -45,6 +46,13 @@ export type VoiceRedisIdempotencyStoreOptions = {
45
46
  ttlSeconds?: number;
46
47
  url?: string;
47
48
  };
49
+ export type VoiceRedisTelephonyWebhookIdempotencyClient = Pick<RedisClient, 'get' | 'set'>;
50
+ export type VoiceRedisTelephonyWebhookIdempotencyStoreOptions = {
51
+ client?: VoiceRedisTelephonyWebhookIdempotencyClient;
52
+ keyPrefix?: string;
53
+ ttlSeconds?: number;
54
+ url?: string;
55
+ };
48
56
  export type VoiceWebhookDeliveryWorkerOptions<TEvent extends StoredVoiceIntegrationEvent = StoredVoiceIntegrationEvent> = {
49
57
  deadLetters?: VoiceIntegrationEventStore<TEvent>;
50
58
  events: VoiceIntegrationEventStore<TEvent>;
@@ -305,6 +313,7 @@ export declare const summarizeVoiceOpsTaskQueue: <TTask extends StoredVoiceOpsTa
305
313
  }) => Promise<VoiceOpsTaskQueueSummary> | VoiceOpsTaskQueueSummary;
306
314
  export declare const createVoiceRedisTaskLeaseCoordinator: (options?: VoiceRedisTaskLeaseCoordinatorOptions) => VoiceRedisTaskLeaseCoordinator;
307
315
  export declare const createVoiceRedisIdempotencyStore: (options?: VoiceRedisIdempotencyStoreOptions) => VoiceIdempotencyStore;
316
+ export declare const createVoiceRedisTelephonyWebhookIdempotencyStore: <TResult = unknown>(options?: VoiceRedisTelephonyWebhookIdempotencyStoreOptions) => VoiceTelephonyWebhookIdempotencyStore<TResult>;
308
317
  export declare const createVoiceWebhookDeliveryWorker: <TEvent extends StoredVoiceIntegrationEvent = StoredVoiceIntegrationEvent>(options: VoiceWebhookDeliveryWorkerOptions<TEvent>) => {
309
318
  drain: () => Promise<VoiceWebhookDeliveryWorkerResult>;
310
319
  };
@@ -1,6 +1,7 @@
1
1
  import { type StoredVoiceTraceEvent, type VoiceTraceSinkDeliveryRecord, type VoiceTraceSinkDeliveryStore, type VoiceTraceEventStore } from './trace';
2
2
  import type { StoredVoiceIntegrationEvent, StoredVoiceExternalObjectMap, StoredVoiceOpsTask, VoiceExternalObjectMapStore, VoiceIntegrationEventStore, VoiceOpsTaskStore } from './ops';
3
3
  import type { StoredVoiceCallReviewArtifact, VoiceCallReviewStore } from './testing/review';
4
+ import type { VoiceTelephonyWebhookIdempotencyStore } from './telephonyOutcome';
4
5
  import type { VoiceSessionRecord, VoiceSessionStore } from './types';
5
6
  export type VoiceSQLiteStoreOptions = {
6
7
  path: string;
@@ -23,4 +24,5 @@ export declare const createVoiceSQLiteIntegrationEventStore: <TEvent extends Sto
23
24
  export declare const createVoiceSQLiteExternalObjectMapStore: <TMapping extends StoredVoiceExternalObjectMap = StoredVoiceExternalObjectMap>(options: VoiceSQLiteStoreOptions) => VoiceExternalObjectMapStore<TMapping>;
24
25
  export declare const createVoiceSQLiteTraceEventStore: <TEvent extends StoredVoiceTraceEvent = StoredVoiceTraceEvent>(options: VoiceSQLiteStoreOptions) => VoiceTraceEventStore<TEvent>;
25
26
  export declare const createVoiceSQLiteTraceSinkDeliveryStore: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(options: VoiceSQLiteStoreOptions) => VoiceTraceSinkDeliveryStore<TDelivery>;
27
+ export declare const createVoiceSQLiteTelephonyWebhookIdempotencyStore: <TResult = unknown>(options: VoiceSQLiteStoreOptions) => VoiceTelephonyWebhookIdempotencyStore<TResult>;
26
28
  export declare const createVoiceSQLiteRuntimeStorage: <TSession extends VoiceSessionRecord = VoiceSessionRecord, TReview extends StoredVoiceCallReviewArtifact = StoredVoiceCallReviewArtifact, TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask, TEvent extends StoredVoiceIntegrationEvent = StoredVoiceIntegrationEvent, TMapping extends StoredVoiceExternalObjectMap = StoredVoiceExternalObjectMap, TTrace extends StoredVoiceTraceEvent = StoredVoiceTraceEvent, TTraceDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(options: VoiceSQLiteStoreOptions) => VoiceSQLiteRuntimeStorage<TSession, TReview, TTask, TEvent, TMapping, TTrace, TTraceDelivery>;
@@ -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.73",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",